File indexing completed on 2024-04-28 16:31:55

0001 /***************************************************************************
0002     Copyright (C) 2001-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 "derivedvalue.h"
0026 #include "collection.h"
0027 #include "fieldformat.h"
0028 #include "utils/stringset.h"
0029 #include "tellico_debug.h"
0030 
0031 #include <QStack>
0032 
0033 using namespace Tellico::Data;
0034 using Tellico::Data::DerivedValue;
0035 
0036 DerivedValue::DerivedValue(const QString& valueTemplate_) : m_valueTemplate(valueTemplate_)
0037     , m_keyRx(QLatin1String("^([^:]+):?(-?\\d*)/?(.*)$")) {
0038   m_keyRx.setMinimal(true);
0039 }
0040 
0041 DerivedValue::DerivedValue(FieldPtr field_) : m_keyRx(QLatin1String("^([^:]+):?(-?\\d*)/?(.*)$")) {
0042   Q_ASSERT(field_);
0043   if(!field_->hasFlag(Field::Derived)) {
0044     myWarning() << "using DerivedValue for non-derived field";
0045   } else {
0046     m_valueTemplate = field_->property(QStringLiteral("template"));
0047     m_fieldName = field_->name();
0048   }
0049   m_keyRx.setMinimal(true);
0050 }
0051 
0052 bool DerivedValue::isRecursive(Collection* coll_) const {
0053   Q_ASSERT(coll_);
0054   StringSet fieldNamesFound;
0055   if(!m_fieldName.isEmpty()) {
0056     fieldNamesFound.add(m_fieldName);
0057   }
0058 
0059   QStack<QString> fieldsToCheck;
0060   foreach(const QString& key, templateFields()) {
0061     fieldsToCheck.push(key);
0062   }
0063   while(!fieldsToCheck.isEmpty()) {
0064     QString fieldName = fieldsToCheck.pop();
0065     FieldPtr f = coll_->fieldByName(fieldName);
0066     if(!f) {
0067       f = coll_->fieldByTitle(fieldName);
0068     }
0069     if(!f) {
0070       continue;
0071     }
0072     if(fieldNamesFound.has(f->name())) {
0073       // we have recursion
0074       myLog() << "found recursion, refers to" << f->name() << "more than once";
0075       return true;
0076     } else {
0077       fieldNamesFound.add(f->name());
0078     }
0079     if(f->hasFlag(Field::Derived)) {
0080       DerivedValue dv(f);
0081       foreach(const QString& key, dv.templateFields()) {
0082         fieldsToCheck.push(key);
0083       }
0084     }
0085   }
0086   return false;
0087 }
0088 
0089 QString DerivedValue::value(EntryPtr entry_, bool formatted_) const {
0090   Q_ASSERT(entry_);
0091   Q_ASSERT(entry_->collection());
0092   if(!entry_ || !entry_->collection()) {
0093     return m_valueTemplate;
0094   }
0095 
0096   QString result;
0097 
0098   int endPos;
0099   int curPos = 0;
0100   int pctPos = m_valueTemplate.indexOf(QLatin1Char('%'), curPos);
0101   while(pctPos != -1 && pctPos+1 < m_valueTemplate.length()) {
0102     if(m_valueTemplate.at(pctPos+1) == QLatin1Char('{')) {
0103       endPos = m_valueTemplate.indexOf(QLatin1Char('}'), pctPos+2);
0104       if(endPos > -1) {
0105         result += m_valueTemplate.midRef(curPos, pctPos-curPos)
0106                 + templateKeyValue(entry_, m_valueTemplate.mid(pctPos+2, endPos-pctPos-2), formatted_);
0107         curPos = endPos+1;
0108       } else {
0109         break;
0110       }
0111     } else {
0112       result += m_valueTemplate.midRef(curPos, pctPos-curPos+1);
0113       curPos = pctPos+1;
0114     }
0115     pctPos = m_valueTemplate.indexOf(QLatin1Char('%'), curPos);
0116   }
0117   result += m_valueTemplate.midRef(curPos, m_valueTemplate.length()-curPos);
0118 //  myDebug() << "format_ << " = " << result;
0119   // sometimes field value might empty, resulting in multiple consecutive white spaces
0120   // so let's simplify that...
0121   return result.simplified();
0122 }
0123 
0124 // format is something like "%{year} %{author}"
0125 QStringList DerivedValue::templateFields() const {
0126   QRegExp rx(QLatin1String("%\\{([^:]+):?.*\\}"));
0127   rx.setMinimal(true);
0128 
0129   QStringList list;
0130   for(int pos = rx.indexIn(m_valueTemplate); pos > -1; pos = rx.indexIn(m_valueTemplate, pos+rx.matchedLength())) {
0131     list << rx.cap(1);
0132   }
0133   return list;
0134 }
0135 
0136 QString DerivedValue::templateKeyValue(EntryPtr entry_, const QString& key_, bool formatted_) const {
0137   if(m_keyRx.indexIn(key_) == -1) {
0138     myDebug() << "unmatched regexp for" << key_;
0139     return QLatin1String("%{") + key_ + QLatin1Char('}');
0140   }
0141 
0142   const QString fieldName = m_keyRx.cap(1);
0143   FieldPtr field = entry_->collection()->fieldByName(fieldName);
0144   if(!field) {
0145     // allow the user to also use field titles
0146     field = entry_->collection()->fieldByTitle(fieldName);
0147   }
0148   if(!field) {
0149     if(fieldName == QLatin1String("@id") ||
0150        fieldName == QLatin1String("id")) {
0151       // '@id' is the best way to use it, but formerly, we allowed just 'id'
0152       return QString::number(entry_->id());
0153     } else {
0154       return QLatin1String("%{") + key_ + QLatin1Char('}');
0155     }
0156   }
0157   // field name, followed by optional colon, optional value index (negative), and words after slash
0158   int pos = m_keyRx.cap(2).toInt();
0159   QString result;
0160   if(pos == 0) {
0161     // insert field value
0162     result = formatted_ ? entry_->formattedField(field) : entry_->field(field);
0163   } else {
0164     QStringList values;
0165     if(field->type() ==  Field::Table) {
0166       // for tables, only take first column
0167       QStringList rows = FieldFormat::splitTable(formatted_ ? entry_->formattedField(field) : entry_->field(field));
0168       foreach(const QString& row, rows) {
0169         const QStringList rowValues = FieldFormat::splitRow(row);
0170         if(!rowValues.isEmpty()) {
0171           values.append(rowValues.at(0));
0172         }
0173       }
0174     } else {
0175       values = FieldFormat::splitValue(formatted_ ? entry_->formattedField(field) : entry_->field(field));
0176     }
0177     if(pos < 0) {
0178       pos += values.count();
0179       if(pos < 0) {
0180         pos = 0;
0181       }
0182     } else {
0183       // a position of 1 is actually index 0
0184       --pos;
0185     }
0186     // use value() instead of at() since not sure within bounds
0187     result = values.value(pos);
0188   }
0189 
0190   const QString func = m_keyRx.cap(3);
0191   if(func.contains(QLatin1Char('u'))) {
0192     result = result.toUpper();
0193   }
0194   if(func.contains(QLatin1Char('l'))) {
0195     result = result.toLower();
0196   }
0197 
0198   return result;
0199 }