xlsxutility.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /****************************************************************************
  2. ** Copyright (c) 2013-2014 Debao Zhang <hello@debao.me>
  3. ** All right reserved.
  4. **
  5. ** Permission is hereby granted, free of charge, to any person obtaining
  6. ** a copy of this software and associated documentation files (the
  7. ** "Software"), to deal in the Software without restriction, including
  8. ** without limitation the rights to use, copy, modify, merge, publish,
  9. ** distribute, sublicense, and/or sell copies of the Software, and to
  10. ** permit persons to whom the Software is furnished to do so, subject to
  11. ** the following conditions:
  12. **
  13. ** The above copyright notice and this permission notice shall be
  14. ** included in all copies or substantial portions of the Software.
  15. **
  16. ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. ** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20. ** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21. ** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22. ** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. **
  24. ****************************************************************************/
  25. #include "xlsxutility_p.h"
  26. #include "xlsxcellreference.h"
  27. #include <QString>
  28. #include <QPoint>
  29. #include <QRegularExpression>
  30. #include <QMap>
  31. #include <QStringList>
  32. #include <QColor>
  33. #include <QDateTime>
  34. #include <QDebug>
  35. namespace QXlsx {
  36. bool parseXsdBoolean(const QString &value, bool defaultValue)
  37. {
  38. if (value == QLatin1String("1") || value == QLatin1String("true"))
  39. return true;
  40. if (value == QLatin1String("0") || value == QLatin1String("false"))
  41. return false;
  42. return defaultValue;
  43. }
  44. QStringList splitPath(const QString &path)
  45. {
  46. int idx = path.lastIndexOf(QLatin1Char('/'));
  47. if (idx == -1)
  48. return QStringList()<<QStringLiteral(".")<<path;
  49. return QStringList()<<path.left(idx)<<path.mid(idx+1);
  50. }
  51. /*
  52. * Return the .rel file path based on filePath
  53. */
  54. QString getRelFilePath(const QString &filePath)
  55. {
  56. int idx = filePath.lastIndexOf(QLatin1Char('/'));
  57. if (idx == -1)
  58. return QString();
  59. return QString(filePath.left(idx) + QLatin1String("/_rels/")
  60. + filePath.mid(idx+1) + QLatin1String(".rels"));
  61. }
  62. double datetimeToNumber(const QDateTime &dt, bool is1904)
  63. {
  64. //Note, for number 0, Excel2007 shown as 1900-1-0, which should be 1899-12-31
  65. QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0));
  66. double excel_time = epoch.msecsTo(dt) / (1000*60*60*24.0);
  67. #if QT_VERSION >= 0x050200
  68. if (dt.isDaylightTime()) // Add one hour if the date is Daylight
  69. excel_time += 1.0 / 24.0;
  70. #endif
  71. if (!is1904 && excel_time > 59) {//31+28
  72. //Account for Excel erroneously treating 1900 as a leap year.
  73. excel_time += 1;
  74. }
  75. return excel_time;
  76. }
  77. double timeToNumber(const QTime &time)
  78. {
  79. return QTime(0,0).msecsTo(time) / (1000*60*60*24.0);
  80. }
  81. QDateTime datetimeFromNumber(double num, bool is1904)
  82. {
  83. if (!is1904 && num > 60)
  84. num = num - 1;
  85. qint64 msecs = static_cast<qint64>(num * 1000*60*60*24.0 + 0.5);
  86. QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0));
  87. QDateTime dt = epoch.addMSecs(msecs);
  88. #if QT_VERSION >= 0x050200
  89. // Remove one hour to see whether the date is Daylight
  90. QDateTime dt2 = dt.addMSecs(-3600);
  91. if (dt2.isDaylightTime())
  92. return dt2;
  93. #endif
  94. return dt;
  95. }
  96. /*
  97. Creates a valid sheet name
  98. minimum length is 1
  99. maximum length is 31
  100. doesn't contain special chars: / \ ? * ] [ :
  101. Sheet names must not begin or end with ' (apostrophe)
  102. Invalid characters are replaced by one space character ' '.
  103. */
  104. QString createSafeSheetName(const QString &nameProposal)
  105. {
  106. if (nameProposal.isEmpty())
  107. return QString();
  108. QString ret = nameProposal;
  109. if (nameProposal.length() > 2 && nameProposal.startsWith(QLatin1Char('\'')) && nameProposal.endsWith(QLatin1Char('\'')))
  110. ret = unescapeSheetName(ret);
  111. //Replace invalid chars with space.
  112. if (nameProposal.contains(QRegularExpression(QStringLiteral("[/\\\\?*\\][:]"))))
  113. ret.replace(QRegularExpression(QStringLiteral("[/\\\\?*\\][:]")), QStringLiteral(" "));
  114. if (ret.startsWith(QLatin1Char('\'')))
  115. ret[0] = QLatin1Char(' ');
  116. if (ret.endsWith(QLatin1Char('\'')))
  117. ret[ret.size()-1] = QLatin1Char(' ');
  118. if (ret.size() > 31)
  119. ret = ret.left(31);
  120. return ret;
  121. }
  122. /*
  123. * When sheetName contains space or apostrophe, escaped is needed by cellFormula/definedName/chartSerials.
  124. */
  125. QString escapeSheetName(const QString &sheetName)
  126. {
  127. //Already escaped.
  128. Q_ASSERT(!sheetName.startsWith(QLatin1Char('\'')) && !sheetName.endsWith(QLatin1Char('\'')));
  129. //These is no need to escape
  130. if (!sheetName.contains(QRegularExpression(QStringLiteral("[ +\\-,%^=<>'&]"))))
  131. return sheetName;
  132. //OK, escape is needed.
  133. QString name = sheetName;
  134. name.replace(QLatin1Char('\''), QLatin1String("\'\'"));
  135. return QLatin1Char('\'') + name + QLatin1Char('\'');
  136. }
  137. /*
  138. */
  139. QString unescapeSheetName(const QString &sheetName)
  140. {
  141. Q_ASSERT(sheetName.length() > 2 && sheetName.startsWith(QLatin1Char('\'')) && sheetName.endsWith(QLatin1Char('\'')));
  142. QString name = sheetName.mid(1, sheetName.length()-2);
  143. name.replace(QLatin1String("\'\'"), QLatin1String("\'"));
  144. return name;
  145. }
  146. /*
  147. * whether the string s starts or ends with space
  148. */
  149. bool isSpaceReserveNeeded(const QString &s)
  150. {
  151. QString spaces(QStringLiteral(" \t\n\r"));
  152. return !s.isEmpty() && (spaces.contains(s.at(0))||spaces.contains(s.at(s.length()-1)));
  153. }
  154. /*
  155. * Convert shared formula for non-root cells.
  156. *
  157. * For example, if "B1:B10" have shared formula "=A1*A1", this function will return "=A2*A2"
  158. * for "B2" cell, "=A3*A3" for "B3" cell, etc.
  159. *
  160. * Note, the formula "=A1*A1" for B1 can also be written as "=RC[-1]*RC[-1]", which is the same
  161. * for all other cells. In other words, this formula is shared.
  162. *
  163. * For long run, we need a formula parser.
  164. */
  165. QString convertSharedFormula(const QString &rootFormula, const CellReference &rootCell, const CellReference &cell)
  166. {
  167. //Find all the "$?[A-Z]+$?[0-9]+" patterns in the rootFormula.
  168. QList<QPair<QString, int> > segments;
  169. QString segment;
  170. bool inQuote = false;
  171. enum RefState{INVALID, PRE_AZ, AZ, PRE_09, _09};
  172. RefState refState = INVALID;
  173. int refFlag = 0; // 0x00, 0x01, 0x02, 0x03 ==> A1, $A1, A$1, $A$1
  174. foreach (QChar ch, rootFormula) {
  175. if (inQuote) {
  176. segment.append(ch);
  177. if (ch == QLatin1Char('"'))
  178. inQuote = false;
  179. } else {
  180. if (ch == QLatin1Char('"')) {
  181. inQuote = true;
  182. refState = INVALID;
  183. segment.append(ch);
  184. } else if (ch == QLatin1Char('$')) {
  185. if (refState == AZ) {
  186. segment.append(ch);
  187. refState = PRE_09;
  188. refFlag |= 0x02;
  189. } else {
  190. segments.append(qMakePair(segment, refState==_09 ? refFlag : -1));
  191. segment = QString(ch); //Start new segment.
  192. refState = PRE_AZ;
  193. refFlag = 0x01;
  194. }
  195. } else if (ch >= QLatin1Char('A') && ch <=QLatin1Char('Z')) {
  196. if (refState == PRE_AZ || refState == AZ) {
  197. segment.append(ch);
  198. } else {
  199. segments.append(qMakePair(segment, refState==_09 ? refFlag : -1));
  200. segment = QString(ch); //Start new segment.
  201. refFlag = 0x00;
  202. }
  203. refState = AZ;
  204. } else if (ch >= QLatin1Char('0') && ch <=QLatin1Char('9')) {
  205. segment.append(ch);
  206. if (refState == AZ || refState == PRE_09 || refState == _09)
  207. refState = _09;
  208. else
  209. refState = INVALID;
  210. } else {
  211. if (refState == _09) {
  212. segments.append(qMakePair(segment, refFlag));
  213. segment = QString(ch); //Start new segment.
  214. } else {
  215. segment.append(ch);
  216. }
  217. refState = INVALID;
  218. }
  219. }
  220. }
  221. if (!segment.isEmpty())
  222. segments.append(qMakePair(segment, refState==_09 ? refFlag : -1));
  223. //Replace "A1", "$A1", "A$1" segment with proper one.
  224. QStringList result;
  225. typedef QPair<QString, int> PairType;
  226. foreach (PairType p, segments) {
  227. //qDebug()<<p.first<<p.second;
  228. if (p.second != -1 && p.second != 3) {
  229. CellReference oldRef(p.first);
  230. int row = p.second & 0x02 ? oldRef.row() : oldRef.row()-rootCell.row()+cell.row();
  231. int col = p.second & 0x01 ? oldRef.column() : oldRef.column()-rootCell.column()+cell.column();
  232. result.append(CellReference(row, col).toString(p.second & 0x02, p.second & 0x01));
  233. } else {
  234. result.append(p.first);
  235. }
  236. }
  237. //OK
  238. return result.join(QString());
  239. }
  240. } //namespace QXlsx