282 lines
9.3 KiB
C++
282 lines
9.3 KiB
C++
|
/****************************************************************************
|
||
|
** Copyright (c) 2013-2014 Debao Zhang <hello@debao.me>
|
||
|
** All right reserved.
|
||
|
**
|
||
|
** Permission is hereby granted, free of charge, to any person obtaining
|
||
|
** a copy of this software and associated documentation files (the
|
||
|
** "Software"), to deal in the Software without restriction, including
|
||
|
** without limitation the rights to use, copy, modify, merge, publish,
|
||
|
** distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
** permit persons to whom the Software is furnished to do so, subject to
|
||
|
** the following conditions:
|
||
|
**
|
||
|
** The above copyright notice and this permission notice shall be
|
||
|
** included in all copies or substantial portions of the Software.
|
||
|
**
|
||
|
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
**
|
||
|
****************************************************************************/
|
||
|
#include "xlsxutility_p.h"
|
||
|
#include "xlsxcellreference.h"
|
||
|
|
||
|
#include <QString>
|
||
|
#include <QPoint>
|
||
|
#include <QRegularExpression>
|
||
|
#include <QMap>
|
||
|
#include <QStringList>
|
||
|
#include <QColor>
|
||
|
#include <QDateTime>
|
||
|
#include <QDebug>
|
||
|
|
||
|
namespace QXlsx {
|
||
|
|
||
|
bool parseXsdBoolean(const QString &value, bool defaultValue)
|
||
|
{
|
||
|
if (value == QLatin1String("1") || value == QLatin1String("true"))
|
||
|
return true;
|
||
|
if (value == QLatin1String("0") || value == QLatin1String("false"))
|
||
|
return false;
|
||
|
return defaultValue;
|
||
|
}
|
||
|
|
||
|
QStringList splitPath(const QString &path)
|
||
|
{
|
||
|
int idx = path.lastIndexOf(QLatin1Char('/'));
|
||
|
if (idx == -1)
|
||
|
return QStringList() << QStringLiteral(".") << path;
|
||
|
|
||
|
return QStringList() << path.left(idx) << path.mid(idx + 1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Return the .rel file path based on filePath
|
||
|
*/
|
||
|
QString getRelFilePath(const QString &filePath)
|
||
|
{
|
||
|
int idx = filePath.lastIndexOf(QLatin1Char('/'));
|
||
|
if (idx == -1)
|
||
|
return QString();
|
||
|
|
||
|
return QString(filePath.left(idx) + QLatin1String("/_rels/") + filePath.mid(idx + 1)
|
||
|
+ QLatin1String(".rels"));
|
||
|
}
|
||
|
|
||
|
double datetimeToNumber(const QDateTime &dt, bool is1904)
|
||
|
{
|
||
|
// Note, for number 0, Excel2007 shown as 1900-1-0, which should be 1899-12-31
|
||
|
QDateTime epoch(is1904 ? QDate(1904, 1, 1) : QDate(1899, 12, 31), QTime(0, 0));
|
||
|
|
||
|
double excel_time = epoch.msecsTo(dt) / (1000 * 60 * 60 * 24.0);
|
||
|
|
||
|
#if QT_VERSION >= 0x050200
|
||
|
if (dt.isDaylightTime()) // Add one hour if the date is Daylight
|
||
|
excel_time += 1.0 / 24.0;
|
||
|
#endif
|
||
|
|
||
|
if (!is1904 && excel_time > 59) { // 31+28
|
||
|
// Account for Excel erroneously treating 1900 as a leap year.
|
||
|
excel_time += 1;
|
||
|
}
|
||
|
|
||
|
return excel_time;
|
||
|
}
|
||
|
|
||
|
double timeToNumber(const QTime &time)
|
||
|
{
|
||
|
return QTime(0, 0).msecsTo(time) / (1000 * 60 * 60 * 24.0);
|
||
|
}
|
||
|
|
||
|
QDateTime datetimeFromNumber(double num, bool is1904)
|
||
|
{
|
||
|
if (!is1904 && num > 60)
|
||
|
num = num - 1;
|
||
|
|
||
|
qint64 msecs = static_cast<qint64>(num * 1000 * 60 * 60 * 24.0 + 0.5);
|
||
|
QDateTime epoch(is1904 ? QDate(1904, 1, 1) : QDate(1899, 12, 31), QTime(0, 0));
|
||
|
|
||
|
QDateTime dt = epoch.addMSecs(msecs);
|
||
|
|
||
|
#if QT_VERSION >= 0x050200
|
||
|
// Remove one hour to see whether the date is Daylight
|
||
|
QDateTime dt2 = dt.addMSecs(-3600);
|
||
|
if (dt2.isDaylightTime())
|
||
|
return dt2;
|
||
|
#endif
|
||
|
|
||
|
return dt;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Creates a valid sheet name
|
||
|
minimum length is 1
|
||
|
maximum length is 31
|
||
|
doesn't contain special chars: / \ ? * ] [ :
|
||
|
Sheet names must not begin or end with ' (apostrophe)
|
||
|
|
||
|
Invalid characters are replaced by one space character ' '.
|
||
|
*/
|
||
|
QString createSafeSheetName(const QString &nameProposal)
|
||
|
{
|
||
|
if (nameProposal.isEmpty())
|
||
|
return QString();
|
||
|
|
||
|
QString ret = nameProposal;
|
||
|
if (nameProposal.length() > 2 && nameProposal.startsWith(QLatin1Char('\''))
|
||
|
&& nameProposal.endsWith(QLatin1Char('\'')))
|
||
|
ret = unescapeSheetName(ret);
|
||
|
|
||
|
// Replace invalid chars with space.
|
||
|
if (nameProposal.contains(QRegularExpression(QStringLiteral("[/\\\\?*\\][:]"))))
|
||
|
ret.replace(QRegularExpression(QStringLiteral("[/\\\\?*\\][:]")), QStringLiteral(" "));
|
||
|
if (ret.startsWith(QLatin1Char('\'')))
|
||
|
ret[0] = QLatin1Char(' ');
|
||
|
if (ret.endsWith(QLatin1Char('\'')))
|
||
|
ret[ret.size() - 1] = QLatin1Char(' ');
|
||
|
|
||
|
if (ret.size() > 31)
|
||
|
ret = ret.left(31);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* When sheetName contains space or apostrophe, escaped is needed by
|
||
|
* cellFormula/definedName/chartSerials.
|
||
|
*/
|
||
|
QString escapeSheetName(const QString &sheetName)
|
||
|
{
|
||
|
// Already escaped.
|
||
|
Q_ASSERT(!sheetName.startsWith(QLatin1Char('\'')) && !sheetName.endsWith(QLatin1Char('\'')));
|
||
|
|
||
|
// These is no need to escape
|
||
|
if (!sheetName.contains(QRegularExpression(QStringLiteral("[ +\\-,%^=<>'&]"))))
|
||
|
return sheetName;
|
||
|
|
||
|
// OK, escape is needed.
|
||
|
QString name = sheetName;
|
||
|
name.replace(QLatin1Char('\''), QLatin1String("\'\'"));
|
||
|
return QLatin1Char('\'') + name + QLatin1Char('\'');
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*/
|
||
|
QString unescapeSheetName(const QString &sheetName)
|
||
|
{
|
||
|
Q_ASSERT(sheetName.length() > 2 && sheetName.startsWith(QLatin1Char('\''))
|
||
|
&& sheetName.endsWith(QLatin1Char('\'')));
|
||
|
|
||
|
QString name = sheetName.mid(1, sheetName.length() - 2);
|
||
|
name.replace(QLatin1String("\'\'"), QLatin1String("\'"));
|
||
|
return name;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* whether the string s starts or ends with space
|
||
|
*/
|
||
|
bool isSpaceReserveNeeded(const QString &s)
|
||
|
{
|
||
|
QString spaces(QStringLiteral(" \t\n\r"));
|
||
|
return !s.isEmpty() && (spaces.contains(s.at(0)) || spaces.contains(s.at(s.length() - 1)));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Convert shared formula for non-root cells.
|
||
|
*
|
||
|
* For example, if "B1:B10" have shared formula "=A1*A1", this function will return "=A2*A2"
|
||
|
* for "B2" cell, "=A3*A3" for "B3" cell, etc.
|
||
|
*
|
||
|
* Note, the formula "=A1*A1" for B1 can also be written as "=RC[-1]*RC[-1]", which is the same
|
||
|
* for all other cells. In other words, this formula is shared.
|
||
|
*
|
||
|
* For long run, we need a formula parser.
|
||
|
*/
|
||
|
QString convertSharedFormula(const QString &rootFormula, const CellReference &rootCell,
|
||
|
const CellReference &cell)
|
||
|
{
|
||
|
// Find all the "$?[A-Z]+$?[0-9]+" patterns in the rootFormula.
|
||
|
QList<QPair<QString, int>> segments;
|
||
|
|
||
|
QString segment;
|
||
|
bool inQuote = false;
|
||
|
enum RefState { INVALID, PRE_AZ, AZ, PRE_09, _09 };
|
||
|
RefState refState = INVALID;
|
||
|
int refFlag = 0; // 0x00, 0x01, 0x02, 0x03 ==> A1, $A1, A$1, $A$1
|
||
|
foreach (QChar ch, rootFormula) {
|
||
|
if (inQuote) {
|
||
|
segment.append(ch);
|
||
|
if (ch == QLatin1Char('"'))
|
||
|
inQuote = false;
|
||
|
} else {
|
||
|
if (ch == QLatin1Char('"')) {
|
||
|
inQuote = true;
|
||
|
refState = INVALID;
|
||
|
segment.append(ch);
|
||
|
} else if (ch == QLatin1Char('$')) {
|
||
|
if (refState == AZ) {
|
||
|
segment.append(ch);
|
||
|
refState = PRE_09;
|
||
|
refFlag |= 0x02;
|
||
|
} else {
|
||
|
segments.append(qMakePair(segment, refState == _09 ? refFlag : -1));
|
||
|
segment = QString(ch); // Start new segment.
|
||
|
refState = PRE_AZ;
|
||
|
refFlag = 0x01;
|
||
|
}
|
||
|
} else if (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z')) {
|
||
|
if (refState == PRE_AZ || refState == AZ) {
|
||
|
segment.append(ch);
|
||
|
} else {
|
||
|
segments.append(qMakePair(segment, refState == _09 ? refFlag : -1));
|
||
|
segment = QString(ch); // Start new segment.
|
||
|
refFlag = 0x00;
|
||
|
}
|
||
|
refState = AZ;
|
||
|
} else if (ch >= QLatin1Char('0') && ch <= QLatin1Char('9')) {
|
||
|
segment.append(ch);
|
||
|
|
||
|
if (refState == AZ || refState == PRE_09 || refState == _09)
|
||
|
refState = _09;
|
||
|
else
|
||
|
refState = INVALID;
|
||
|
} else {
|
||
|
if (refState == _09) {
|
||
|
segments.append(qMakePair(segment, refFlag));
|
||
|
segment = QString(ch); // Start new segment.
|
||
|
} else {
|
||
|
segment.append(ch);
|
||
|
}
|
||
|
refState = INVALID;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!segment.isEmpty())
|
||
|
segments.append(qMakePair(segment, refState == _09 ? refFlag : -1));
|
||
|
|
||
|
// Replace "A1", "$A1", "A$1" segment with proper one.
|
||
|
QStringList result;
|
||
|
typedef QPair<QString, int> PairType;
|
||
|
foreach (PairType p, segments) {
|
||
|
// qDebug()<<p.first<<p.second;
|
||
|
if (p.second != -1 && p.second != 3) {
|
||
|
CellReference oldRef(p.first);
|
||
|
int row = p.second & 0x02 ? oldRef.row() : oldRef.row() - rootCell.row() + cell.row();
|
||
|
int col = p.second & 0x01 ? oldRef.column()
|
||
|
: oldRef.column() - rootCell.column() + cell.column();
|
||
|
result.append(CellReference(row, col).toString(p.second & 0x02, p.second & 0x01));
|
||
|
} else {
|
||
|
result.append(p.first);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// OK
|
||
|
return result.join(QString());
|
||
|
}
|
||
|
|
||
|
} // namespace QXlsx
|