xlsxutility.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // xlsxutility.cpp
  2. #include "xlsxutility_p.h"
  3. #include "xlsxcellreference.h"
  4. #include <QString>
  5. #include <QPoint>
  6. #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
  7. #include <QRegularExpression>
  8. #else
  9. #include <QRegExp>
  10. #endif
  11. #include <QMap>
  12. #include <QStringList>
  13. #include <QColor>
  14. #include <QDateTime>
  15. #include <QDebug>
  16. #include <cmath>
  17. #include <string>
  18. QT_BEGIN_NAMESPACE_XLSX
  19. bool parseXsdBoolean(const QString &value, bool defaultValue)
  20. {
  21. if (value == QLatin1String("1") || value == QLatin1String("true"))
  22. return true;
  23. if (value == QLatin1String("0") || value == QLatin1String("false"))
  24. return false;
  25. return defaultValue;
  26. }
  27. QStringList splitPath(const QString &path)
  28. {
  29. int idx = path.lastIndexOf(QLatin1Char('/'));
  30. if (idx == -1)
  31. return { QStringLiteral("."), path };
  32. return { path.left(idx), path.mid(idx+1) };
  33. }
  34. /*
  35. * Return the .rel file path based on filePath
  36. */
  37. QString getRelFilePath(const QString &filePath)
  38. {
  39. QString ret;
  40. int idx = filePath.lastIndexOf(QLatin1Char('/'));
  41. if (idx == -1) // not found
  42. {
  43. // return QString();
  44. // dev34
  45. ret = QLatin1String("_rels/") + QStringLiteral("%0.rels").arg(filePath);
  46. return ret;
  47. }
  48. ret = QString( filePath.left(idx) + QLatin1String("/_rels/") + filePath.mid(idx+1) + QLatin1String(".rels"));
  49. return ret;
  50. }
  51. double datetimeToNumber(const QDateTime &dt, bool is1904)
  52. {
  53. //Note, for number 0, Excel2007 shown as 1900-1-0, which should be 1899-12-31
  54. QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0));
  55. double excel_time = epoch.msecsTo(dt) / (1000*60*60*24.0);
  56. #if QT_VERSION >= 0x050200
  57. if (dt.isDaylightTime()) // Add one hour if the date is Daylight
  58. excel_time += 1.0 / 24.0;
  59. #endif
  60. if (!is1904 && excel_time > 59) {//31+28
  61. //Account for Excel erroneously treating 1900 as a leap year.
  62. excel_time += 1;
  63. }
  64. return excel_time;
  65. }
  66. double timeToNumber(const QTime &time)
  67. {
  68. return QTime(0,0).msecsTo(time) / (1000*60*60*24.0);
  69. }
  70. QVariant datetimeFromNumber(double num, bool is1904)
  71. {
  72. QDateTime dtRet; // return value
  73. if (!is1904 && num > 60) // for mac os excel
  74. {
  75. num = num - 1;
  76. }
  77. qint64 msecs = static_cast<qint64>(num * 1000*60*60*24.0 + 0.5);
  78. QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0));
  79. QDateTime dtOld = epoch.addMSecs(msecs);
  80. dtRet = dtOld;
  81. #if QT_VERSION >= 0x050200
  82. // Remove one hour to see whether the date is Daylight
  83. QDateTime dtNew = dtRet.addMSecs( -3600000 ); // issue102
  84. if ( dtNew.isDaylightTime() )
  85. {
  86. dtRet = dtNew;
  87. }
  88. #endif
  89. float whole = 0;
  90. float fractional = std::modf(num, &whole);
  91. if ( num < double(1) )
  92. {
  93. // only time
  94. QTime t = dtRet.time();
  95. return QVariant(t);
  96. }
  97. if ( fractional == 0.0 )
  98. {
  99. // only date
  100. QDate onlyDT = dtRet.date();
  101. return QVariant(onlyDT);
  102. }
  103. return QVariant(dtRet);
  104. }
  105. /*
  106. Creates a valid sheet name
  107. minimum length is 1
  108. maximum length is 31
  109. doesn't contain special chars: / \ ? * ] [ :
  110. Sheet names must not begin or end with ' (apostrophe)
  111. Invalid characters are replaced by one space character ' '.
  112. */
  113. QString createSafeSheetName(const QString &nameProposal)
  114. {
  115. if (nameProposal.isEmpty())
  116. return QString();
  117. QString ret = nameProposal;
  118. if (nameProposal.length() > 2 && nameProposal.startsWith(QLatin1Char('\'')) && nameProposal.endsWith(QLatin1Char('\'')))
  119. ret = unescapeSheetName(ret);
  120. //Replace invalid chars with space.
  121. #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
  122. if (nameProposal.contains(QRegularExpression(QStringLiteral("[/\\\\?*\\][:]"))))
  123. ret.replace(QRegularExpression(QStringLiteral("[/\\\\?*\\][:]")), QStringLiteral(" "));
  124. #else
  125. if (nameProposal.contains(QRegExp(QLatin1String("[/\\\\?*\\][:]"))))
  126. ret.replace(QRegExp(QLatin1String("[/\\\\?*\\][:]")), QLatin1String(" "));
  127. #endif
  128. if (ret.startsWith(QLatin1Char('\'')))
  129. ret[0] = QLatin1Char(' ');
  130. if (ret.endsWith(QLatin1Char('\'')))
  131. ret[ret.size()-1] = QLatin1Char(' ');
  132. if (ret.size() > 31)
  133. ret = ret.left(31);
  134. return ret;
  135. }
  136. /*
  137. * When sheetName contains space or apostrophe, escaped is needed by cellFormula/definedName/chartSerials.
  138. */
  139. QString escapeSheetName(const QString &sheetName)
  140. {
  141. //Already escaped.
  142. Q_ASSERT(!sheetName.startsWith(QLatin1Char('\'')) && !sheetName.endsWith(QLatin1Char('\'')));
  143. //These is no need to escape
  144. #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
  145. if (!sheetName.contains(QRegularExpression(QStringLiteral("[ +\\-,%^=<>'&]"))))
  146. return sheetName;
  147. #else
  148. if (!sheetName.contains(QRegExp(QLatin1String("[ +\\-,%^=<>'&]"))))
  149. return sheetName;
  150. #endif
  151. //OK, escape is needed.
  152. QString name = sheetName;
  153. name.replace(QLatin1Char('\''), QLatin1String("\'\'"));
  154. return QLatin1Char('\'') + name + QLatin1Char('\'');
  155. }
  156. /*
  157. */
  158. QString unescapeSheetName(const QString &sheetName)
  159. {
  160. Q_ASSERT(sheetName.length() > 2 && sheetName.startsWith(QLatin1Char('\'')) && sheetName.endsWith(QLatin1Char('\'')));
  161. QString name = sheetName.mid(1, sheetName.length()-2);
  162. name.replace(QLatin1String("\'\'"), QLatin1String("\'"));
  163. return name;
  164. }
  165. /*
  166. * whether the string s starts or ends with space
  167. */
  168. bool isSpaceReserveNeeded(const QString &s)
  169. {
  170. QString spaces(QStringLiteral(" \t\n\r"));
  171. return !s.isEmpty() && (spaces.contains(s.at(0))||spaces.contains(s.at(s.length()-1)));
  172. }
  173. /*
  174. * Convert shared formula for non-root cells.
  175. *
  176. * For example, if "B1:B10" have shared formula "=A1*A1", this function will return "=A2*A2"
  177. * for "B2" cell, "=A3*A3" for "B3" cell, etc.
  178. *
  179. * Note, the formula "=A1*A1" for B1 can also be written as "=RC[-1]*RC[-1]", which is the same
  180. * for all other cells. In other words, this formula is shared.
  181. *
  182. * For long run, we need a formula parser.
  183. */
  184. QString convertSharedFormula(const QString &rootFormula, const CellReference &rootCell, const CellReference &cell)
  185. {
  186. Q_UNUSED(rootCell)
  187. Q_UNUSED(cell)
  188. //Find all the "$?[A-Z]+$?[0-9]+" patterns in the rootFormula.
  189. QVector<std::pair<QString, int> > segments;
  190. QString segment;
  191. bool inQuote = false;
  192. enum RefState{INVALID, PRE_AZ, AZ, PRE_09, _09};
  193. RefState refState = INVALID;
  194. int refFlag = 0; // 0x00, 0x01, 0x02, 0x03 ==> A1, $A1, A$1, $A$1
  195. for (QChar ch : rootFormula) {
  196. if (inQuote) {
  197. segment.append(ch);
  198. if (ch == QLatin1Char('"'))
  199. inQuote = false;
  200. } else {
  201. if (ch == QLatin1Char('"')) {
  202. inQuote = true;
  203. refState = INVALID;
  204. segment.append(ch);
  205. } else if (ch == QLatin1Char('$')) {
  206. if (refState == AZ) {
  207. segment.append(ch);
  208. refState = PRE_09;
  209. refFlag |= 0x02;
  210. } else {
  211. segments.append(std::make_pair(segment, refState==_09 ? refFlag : -1 ));
  212. segment = QString(ch); //Start new segment.
  213. refState = PRE_AZ;
  214. refFlag = 0x01;
  215. }
  216. } else if (ch >= QLatin1Char('A') && ch <=QLatin1Char('Z')) {
  217. if (refState == PRE_AZ || refState == AZ) {
  218. segment.append(ch);
  219. } else {
  220. segments.append(std::make_pair(segment, refState==_09 ? refFlag : -1 ));
  221. segment = QString(ch); //Start new segment.
  222. refFlag = 0x00;
  223. }
  224. refState = AZ;
  225. } else if (ch >= QLatin1Char('0') && ch <=QLatin1Char('9')) {
  226. segment.append(ch);
  227. if (refState == AZ || refState == PRE_09 || refState == _09)
  228. refState = _09;
  229. else
  230. refState = INVALID;
  231. } else {
  232. if (refState == _09) {
  233. segments.append(std::make_pair(segment, refFlag ));
  234. segment = QString(ch); //Start new segment.
  235. } else {
  236. segment.append(ch);
  237. }
  238. refState = INVALID;
  239. }
  240. }
  241. }
  242. if (!segment.isEmpty())
  243. segments.append(std::make_pair(segment, refState==_09 ? refFlag : -1 ));
  244. //Replace "A1", "$A1", "A$1" segment with proper one.
  245. QStringList result;
  246. for (const auto &p : segments) {
  247. //qDebug()<<p.first<<p.second;
  248. if (p.second != -1 && p.second != 3) {
  249. CellReference oldRef(p.first);
  250. int row = p.second & 0x02 ? oldRef.row() : oldRef.row()-rootCell.row()+cell.row();
  251. int col = p.second & 0x01 ? oldRef.column() : oldRef.column()-rootCell.column()+cell.column();
  252. result.append(CellReference(row, col).toString(p.second & 0x02, p.second & 0x01));
  253. } else {
  254. result.append(p.first);
  255. }
  256. }
  257. //OK
  258. return result.join(QString());
  259. }
  260. QT_END_NAMESPACE_XLSX