File indexing completed on 2025-03-23 03:32:51
0001 // xlsxutility.cpp 0002 0003 #include "xlsxutility_p.h" 0004 #include "xlsxcellreference.h" 0005 0006 #include <QString> 0007 #include <QPoint> 0008 #include <QRegularExpression> 0009 #include <QMap> 0010 #include <QStringList> 0011 #include <QColor> 0012 #include <QDateTime> 0013 #include <QDebug> 0014 0015 #include <cmath> 0016 #include <string> 0017 0018 QT_BEGIN_NAMESPACE_XLSX 0019 0020 bool parseXsdBoolean(const QString &value, bool defaultValue) 0021 { 0022 if (value == QLatin1String("1") || value == QLatin1String("true")) 0023 return true; 0024 if (value == QLatin1String("0") || value == QLatin1String("false")) 0025 return false; 0026 return defaultValue; 0027 } 0028 0029 QStringList splitPath(const QString &path) 0030 { 0031 int idx = path.lastIndexOf(QLatin1Char('/')); 0032 if (idx == -1) 0033 return { QStringLiteral("."), path }; 0034 0035 return { path.left(idx), path.mid(idx+1) }; 0036 } 0037 0038 /* 0039 * Return the .rel file path based on filePath 0040 */ 0041 QString getRelFilePath(const QString &filePath) 0042 { 0043 QString ret; 0044 0045 int idx = filePath.lastIndexOf(QLatin1Char('/')); 0046 if (idx == -1) // not found 0047 { 0048 // return QString(); 0049 0050 // dev34 0051 ret = QLatin1String("_rels/") + QStringLiteral("%0.rels").arg(filePath); 0052 return ret; 0053 } 0054 0055 ret = QString( filePath.left(idx) + QLatin1String("/_rels/") + filePath.mid(idx+1) + QLatin1String(".rels")); 0056 return ret; 0057 } 0058 0059 double datetimeToNumber(const QDateTime &dt, bool is1904) 0060 { 0061 //Note, for number 0, Excel2007 shown as 1900-1-0, which should be 1899-12-31 0062 QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0)); 0063 0064 double excel_time = epoch.msecsTo(dt) / (1000*60*60*24.0); 0065 0066 if (dt.isDaylightTime()) // Add one hour if the date is Daylight 0067 excel_time += 1.0 / 24.0; 0068 0069 if (!is1904 && excel_time > 59) {//31+28 0070 //Account for Excel erroneously treating 1900 as a leap year. 0071 excel_time += 1; 0072 } 0073 0074 return excel_time; 0075 } 0076 0077 double timeToNumber(const QTime &time) 0078 { 0079 return QTime(0,0).msecsTo(time) / (1000*60*60*24.0); 0080 } 0081 0082 QVariant datetimeFromNumber(double num, bool is1904) 0083 { 0084 QDateTime dtRet; // return value 0085 0086 if (!is1904 && num > 60) // for mac os excel 0087 { 0088 num = num - 1; 0089 } 0090 0091 qint64 msecs = static_cast<qint64>(num * 1000*60*60*24.0 + 0.5); 0092 QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0)); 0093 QDateTime dtOld = epoch.addMSecs(msecs); 0094 dtRet = dtOld; 0095 0096 // Remove one hour to see whether the date is Daylight 0097 QDateTime dtNew = dtRet.addMSecs( -3600000 ); // issue102 0098 if ( dtNew.isDaylightTime() ) 0099 { 0100 dtRet = dtNew; 0101 } 0102 0103 double whole = 0; 0104 double fractional = std::modf(num, &whole); 0105 0106 if ( num < double(1) ) 0107 { 0108 // only time 0109 QTime t = dtRet.time(); 0110 return QVariant(t); 0111 } 0112 0113 if ( fractional == 0.0 ) 0114 { 0115 // only date 0116 QDate onlyDT = dtRet.date(); 0117 return QVariant(onlyDT); 0118 } 0119 0120 return QVariant(dtRet); 0121 } 0122 0123 /* 0124 Creates a valid sheet name 0125 minimum length is 1 0126 maximum length is 31 0127 doesn't contain special chars: / \ ? * ] [ : 0128 Sheet names must not begin or end with ' (apostrophe) 0129 0130 Invalid characters are replaced by one space character ' '. 0131 */ 0132 QString createSafeSheetName(const QString &nameProposal) 0133 { 0134 if (nameProposal.isEmpty()) 0135 return QString(); 0136 0137 QString ret = nameProposal; 0138 if (nameProposal.length() > 2 && nameProposal.startsWith(QLatin1Char('\'')) && nameProposal.endsWith(QLatin1Char('\''))) 0139 ret = unescapeSheetName(ret); 0140 0141 //Replace invalid chars with space. 0142 static QRegularExpression invalidChars(QStringLiteral("[/\\\\?*\\][:]")); 0143 if (nameProposal.contains(invalidChars)) { 0144 static QRegularExpression validChars(QStringLiteral("[/\\\\?*\\][:]")); 0145 ret.replace(validChars, QStringLiteral(" ")); 0146 } 0147 0148 if (ret.startsWith(QLatin1Char('\''))) 0149 ret[0] = QLatin1Char(' '); 0150 0151 if (ret.endsWith(QLatin1Char('\''))) 0152 ret[ret.size()-1] = QLatin1Char(' '); 0153 0154 if (ret.size() > 31) 0155 ret = ret.left(31); 0156 return ret; 0157 } 0158 0159 /* 0160 * When sheetName contains space or apostrophe, escaped is needed by cellFormula/definedName/chartSerials. 0161 */ 0162 QString escapeSheetName(const QString &sheetName) 0163 { 0164 //Already escaped. 0165 Q_ASSERT(!sheetName.startsWith(QLatin1Char('\'')) && !sheetName.endsWith(QLatin1Char('\''))); 0166 0167 //These is no need to escape 0168 static const auto escape = QRegularExpression(QStringLiteral("[ +\\-,%^=<>'&]")); 0169 if (!sheetName.contains(escape)) 0170 return sheetName; 0171 0172 //OK, escape is needed. 0173 QString name = sheetName; 0174 name.replace(QLatin1Char('\''), QLatin1String("\'\'")); 0175 return QLatin1Char('\'') + name + QLatin1Char('\''); 0176 } 0177 0178 /* 0179 */ 0180 QString unescapeSheetName(const QString &sheetName) 0181 { 0182 Q_ASSERT(sheetName.length() > 2 && sheetName.startsWith(QLatin1Char('\'')) && sheetName.endsWith(QLatin1Char('\''))); 0183 0184 QString name = sheetName.mid(1, sheetName.length()-2); 0185 name.replace(QLatin1String("\'\'"), QLatin1String("\'")); 0186 return name; 0187 } 0188 0189 /* 0190 * whether the string s starts or ends with space 0191 */ 0192 bool isSpaceReserveNeeded(const QString &s) 0193 { 0194 QString spaces(QStringLiteral(" \t\n\r")); 0195 return !s.isEmpty() && (spaces.contains(s.at(0))||spaces.contains(s.at(s.length()-1))); 0196 } 0197 0198 /* 0199 * Convert shared formula for non-root cells. 0200 * 0201 * For example, if "B1:B10" have shared formula "=A1*A1", this function will return "=A2*A2" 0202 * for "B2" cell, "=A3*A3" for "B3" cell, etc. 0203 * 0204 * Note, the formula "=A1*A1" for B1 can also be written as "=RC[-1]*RC[-1]", which is the same 0205 * for all other cells. In other words, this formula is shared. 0206 * 0207 * For long run, we need a formula parser. 0208 */ 0209 QString convertSharedFormula(const QString &rootFormula, const CellReference &rootCell, const CellReference &cell) 0210 { 0211 Q_UNUSED(rootCell) 0212 Q_UNUSED(cell) 0213 //Find all the "$?[A-Z]+$?[0-9]+" patterns in the rootFormula. 0214 QVector<std::pair<QString, int> > segments; 0215 0216 QString segment; 0217 bool inQuote = false; 0218 enum RefState{INVALID, PRE_AZ, AZ, PRE_09, _09}; 0219 RefState refState = INVALID; 0220 int refFlag = 0; // 0x00, 0x01, 0x02, 0x03 ==> A1, $A1, A$1, $A$1 0221 for (QChar ch : rootFormula) { 0222 if (inQuote) { 0223 segment.append(ch); 0224 if (ch == QLatin1Char('"')) 0225 inQuote = false; 0226 } else { 0227 if (ch == QLatin1Char('"')) { 0228 inQuote = true; 0229 refState = INVALID; 0230 segment.append(ch); 0231 } else if (ch == QLatin1Char('$')) { 0232 if (refState == AZ) { 0233 segment.append(ch); 0234 refState = PRE_09; 0235 refFlag |= 0x02; 0236 } else { 0237 segments.append(std::make_pair(segment, refState==_09 ? refFlag : -1 )); 0238 segment = QString(ch); //Start new segment. 0239 refState = PRE_AZ; 0240 refFlag = 0x01; 0241 } 0242 } else if (ch >= QLatin1Char('A') && ch <=QLatin1Char('Z')) { 0243 if (refState == PRE_AZ || refState == AZ) { 0244 segment.append(ch); 0245 } else { 0246 segments.append(std::make_pair(segment, refState==_09 ? refFlag : -1 )); 0247 segment = QString(ch); //Start new segment. 0248 refFlag = 0x00; 0249 } 0250 refState = AZ; 0251 } else if (ch >= QLatin1Char('0') && ch <=QLatin1Char('9')) { 0252 segment.append(ch); 0253 0254 if (refState == AZ || refState == PRE_09 || refState == _09) 0255 refState = _09; 0256 else 0257 refState = INVALID; 0258 } else { 0259 if (refState == _09) { 0260 segments.append(std::make_pair(segment, refFlag )); 0261 segment = QString(ch); //Start new segment. 0262 } else { 0263 segment.append(ch); 0264 } 0265 refState = INVALID; 0266 } 0267 } 0268 } 0269 0270 if (!segment.isEmpty()) 0271 segments.append(std::make_pair(segment, refState==_09 ? refFlag : -1 )); 0272 0273 //Replace "A1", "$A1", "A$1" segment with proper one. 0274 QStringList result; 0275 for (const auto &p : segments) { 0276 //qDebug()<<p.first<<p.second; 0277 if (p.second != -1 && p.second != 3) { 0278 CellReference oldRef(p.first); 0279 int row = p.second & 0x02 ? oldRef.row() : oldRef.row()-rootCell.row()+cell.row(); 0280 int col = p.second & 0x01 ? oldRef.column() : oldRef.column()-rootCell.column()+cell.column(); 0281 result.append(CellReference(row, col).toString(p.second & 0x02, p.second & 0x01)); 0282 } else { 0283 result.append(p.first); 0284 } 0285 } 0286 0287 //OK 0288 return result.join(QString()); 0289 } 0290 0291 QT_END_NAMESPACE_XLSX