Mercurial > clickerconvert
diff src/xlsx/xlsxutility.cpp @ 1:93d3106bb9a4
Add qt xlsx library
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Tue, 22 Mar 2016 10:38:08 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/xlsx/xlsxutility.cpp Tue Mar 22 10:38:08 2016 +0100 @@ -0,0 +1,276 @@ +/**************************************************************************** +** 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