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