File indexing completed on 2024-05-26 03:50:53

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