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 }