diff src/xlsx/xlsxworksheet.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/xlsxworksheet.cpp	Tue Mar 22 10:38:08 2016 +0100
@@ -0,0 +1,2344 @@
+/****************************************************************************
+** 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 "xlsxrichstring.h"
+#include "xlsxcellreference.h"
+#include "xlsxworksheet.h"
+#include "xlsxworksheet_p.h"
+#include "xlsxworkbook.h"
+#include "xlsxformat.h"
+#include "xlsxformat_p.h"
+#include "xlsxutility_p.h"
+#include "xlsxsharedstrings_p.h"
+#include "xlsxdrawing_p.h"
+#include "xlsxstyles_p.h"
+#include "xlsxcell.h"
+#include "xlsxcell_p.h"
+#include "xlsxcellrange.h"
+#include "xlsxconditionalformatting_p.h"
+#include "xlsxdrawinganchor_p.h"
+#include "xlsxchart.h"
+#include "xlsxcellformula.h"
+#include "xlsxcellformula_p.h"
+
+#include <QVariant>
+#include <QDateTime>
+#include <QPoint>
+#include <QFile>
+#include <QUrl>
+#include <QRegularExpression>
+#include <QDebug>
+#include <QBuffer>
+#include <QXmlStreamWriter>
+#include <QXmlStreamReader>
+#include <QTextDocument>
+#include <QDir>
+
+#include <math.h>
+
+QT_BEGIN_NAMESPACE_XLSX
+
+WorksheetPrivate::WorksheetPrivate(Worksheet *p, Worksheet::CreateFlag flag)
+    : AbstractSheetPrivate(p, flag)
+  , windowProtection(false), showFormulas(false), showGridLines(true), showRowColHeaders(true)
+  , showZeros(true), rightToLeft(false), tabSelected(false), showRuler(false)
+  , showOutlineSymbols(true), showWhiteSpace(true), urlPattern(QStringLiteral("^([fh]tt?ps?://)|(mailto:)|(file://)"))
+{
+    previous_row = 0;
+
+    outline_row_level = 0;
+    outline_col_level = 0;
+
+    default_row_height = 15;
+    default_row_zeroed = false;
+}
+
+WorksheetPrivate::~WorksheetPrivate()
+{
+}
+
+/*
+  Calculate the "spans" attribute of the <row> tag. This is an
+  XLSX optimisation and isn't strictly required. However, it
+  makes comparing files easier. The span is the same for each
+  block of 16 rows.
+ */
+void WorksheetPrivate::calculateSpans() const
+{
+    row_spans.clear();
+    int span_min = XLSX_COLUMN_MAX+1;
+    int span_max = -1;
+
+    for (int row_num = dimension.firstRow(); row_num <= dimension.lastRow(); row_num++) {
+        if (cellTable.contains(row_num)) {
+            for (int col_num = dimension.firstColumn(); col_num <= dimension.lastColumn(); col_num++) {
+                if (cellTable[row_num].contains(col_num)) {
+                    if (span_max == -1) {
+                        span_min = col_num;
+                        span_max = col_num;
+                    } else {
+                        if (col_num < span_min)
+                            span_min = col_num;
+                        else if (col_num > span_max)
+                            span_max = col_num;
+                    }
+                }
+            }
+        }
+        if (comments.contains(row_num)) {
+            for (int col_num = dimension.firstColumn(); col_num <= dimension.lastColumn(); col_num++) {
+                if (comments[row_num].contains(col_num)) {
+                    if (span_max == -1) {
+                        span_min = col_num;
+                        span_max = col_num;
+                    } else {
+                        if (col_num < span_min)
+                            span_min = col_num;
+                        else if (col_num > span_max)
+                            span_max = col_num;
+                    }
+                }
+            }
+        }
+
+        if (row_num%16 == 0 || row_num == dimension.lastRow()) {
+            if (span_max != -1) {
+                row_spans[row_num / 16] = QStringLiteral("%1:%2").arg(span_min).arg(span_max);
+                span_min = XLSX_COLUMN_MAX+1;
+                span_max = -1;
+            }
+        }
+    }
+}
+
+
+QString WorksheetPrivate::generateDimensionString() const
+{
+    if (!dimension.isValid())
+        return QStringLiteral("A1");
+    else
+        return dimension.toString();
+}
+
+/*
+  Check that row and col are valid and store the max and min
+  values for use in other methods/elements. The ignore_row /
+  ignore_col flags is used to indicate that we wish to perform
+  the dimension check without storing the value. The ignore
+  flags are use by setRow() and dataValidate.
+*/
+int WorksheetPrivate::checkDimensions(int row, int col, bool ignore_row, bool ignore_col)
+{
+    Q_ASSERT_X(row!=0, "checkDimensions", "row should start from 1 instead of 0");
+    Q_ASSERT_X(col!=0, "checkDimensions", "column should start from 1 instead of 0");
+
+    if (row > XLSX_ROW_MAX || row < 1 || col > XLSX_COLUMN_MAX || col < 1)
+        return -1;
+
+    if (!ignore_row) {
+        if (row < dimension.firstRow() || dimension.firstRow() == -1) dimension.setFirstRow(row);
+        if (row > dimension.lastRow()) dimension.setLastRow(row);
+    }
+    if (!ignore_col) {
+        if (col < dimension.firstColumn() || dimension.firstColumn() == -1) dimension.setFirstColumn(col);
+        if (col > dimension.lastColumn()) dimension.setLastColumn(col);
+    }
+
+    return 0;
+}
+
+/*!
+  \class Worksheet
+  \inmodule QtXlsx
+  \brief Represent one worksheet in the workbook.
+*/
+
+/*!
+ * \internal
+ */
+Worksheet::Worksheet(const QString &name, int id, Workbook *workbook, CreateFlag flag)
+    :AbstractSheet(name, id, workbook, new WorksheetPrivate(this, flag))
+{
+    if (!workbook) //For unit test propose only. Ignore the memery leak.
+        d_func()->workbook = new Workbook(flag);
+}
+
+/*!
+ * \internal
+ *
+ * Make a copy of this sheet.
+ */
+
+Worksheet *Worksheet::copy(const QString &distName, int distId) const
+{
+    Q_D(const Worksheet);
+    Worksheet *sheet = new Worksheet(distName, distId, d->workbook, F_NewFromScratch);
+    WorksheetPrivate *sheet_d = sheet->d_func();
+
+    sheet_d->dimension = d->dimension;
+
+    QMapIterator<int, QMap<int, QSharedPointer<Cell> > > it(d->cellTable);
+    while (it.hasNext()) {
+        it.next();
+        int row = it.key();
+        QMapIterator<int, QSharedPointer<Cell> > it2(it.value());
+        while (it2.hasNext()) {
+            it2.next();
+            int col = it2.key();
+
+            QSharedPointer<Cell> cell(new Cell(it2.value().data()));
+            cell->d_ptr->parent = sheet;
+
+            if (cell->cellType() == Cell::SharedStringType)
+                d->workbook->sharedStrings()->addSharedString(cell->d_ptr->richString);
+
+            sheet_d->cellTable[row][col] = cell;
+        }
+    }
+
+    sheet_d->merges = d->merges;
+//    sheet_d->rowsInfo = d->rowsInfo;
+//    sheet_d->colsInfo = d->colsInfo;
+//    sheet_d->colsInfoHelper = d->colsInfoHelper;
+//    sheet_d->dataValidationsList = d->dataValidationsList;
+//    sheet_d->conditionalFormattingList = d->conditionalFormattingList;
+
+    return sheet;
+}
+
+/*!
+ * Destroys this workssheet.
+ */
+Worksheet::~Worksheet()
+{
+}
+
+/*!
+ * Returns whether sheet is protected.
+ */
+bool Worksheet::isWindowProtected() const
+{
+    Q_D(const Worksheet);
+    return d->windowProtection;
+}
+
+/*!
+ * Protects/unprotects the sheet based on \a protect.
+ */
+void Worksheet::setWindowProtected(bool protect)
+{
+    Q_D(Worksheet);
+    d->windowProtection = protect;
+}
+
+/*!
+ * Return whether formulas instead of their calculated results shown in cells
+ */
+bool Worksheet::isFormulasVisible() const
+{
+    Q_D(const Worksheet);
+    return d->showFormulas;
+}
+
+/*!
+ * Show formulas in cells instead of their calculated results when \a visible is true.
+ */
+void Worksheet::setFormulasVisible(bool visible)
+{
+    Q_D(Worksheet);
+    d->showFormulas = visible;
+}
+
+/*!
+ * Return whether gridlines is shown or not.
+ */
+bool Worksheet::isGridLinesVisible() const
+{
+    Q_D(const Worksheet);
+    return d->showGridLines;
+}
+
+/*!
+ * Show or hide the gridline based on \a visible
+ */
+void Worksheet::setGridLinesVisible(bool visible)
+{
+    Q_D(Worksheet);
+    d->showGridLines = visible;
+}
+
+/*!
+ * Return whether is row and column headers is vislbe.
+ */
+bool Worksheet::isRowColumnHeadersVisible() const
+{
+    Q_D(const Worksheet);
+    return d->showRowColHeaders;
+}
+
+/*!
+ * Show or hide the row column headers based on \a visible
+ */
+void Worksheet::setRowColumnHeadersVisible(bool visible)
+{
+    Q_D(Worksheet);
+    d->showRowColHeaders = visible;
+}
+
+
+/*!
+ * Return whether the sheet is shown right-to-left or not.
+ */
+bool Worksheet::isRightToLeft() const
+{
+    Q_D(const Worksheet);
+    return d->rightToLeft;
+}
+
+/*!
+ * Enable or disable the right-to-left based on \a enable.
+ */
+void Worksheet::setRightToLeft(bool enable)
+{
+    Q_D(Worksheet);
+    d->rightToLeft = enable;
+}
+
+/*!
+ * Return whether is cells that have zero value show a zero.
+ */
+bool Worksheet::isZerosVisible() const
+{
+    Q_D(const Worksheet);
+    return d->showZeros;
+}
+
+/*!
+ * Show a zero in cells that have zero value if \a visible is true.
+ */
+void Worksheet::setZerosVisible(bool visible)
+{
+    Q_D(Worksheet);
+    d->showZeros = visible;
+}
+
+/*!
+ * Return whether this tab is selected.
+ */
+bool Worksheet::isSelected() const
+{
+    Q_D(const Worksheet);
+    return d->tabSelected;
+}
+
+/*!
+ * Select this sheet if \a select is true.
+ */
+void Worksheet::setSelected(bool select)
+{
+    Q_D(Worksheet);
+    d->tabSelected = select;
+}
+
+/*!
+ * Return whether is ruler is shown.
+ */
+bool Worksheet::isRulerVisible() const
+{
+    Q_D(const Worksheet);
+    return d->showRuler;
+
+}
+
+/*!
+ * Show or hide the ruler based on \a visible.
+ */
+void Worksheet::setRulerVisible(bool visible)
+{
+    Q_D(Worksheet);
+    d->showRuler = visible;
+
+}
+
+/*!
+ * Return whether is outline symbols is shown.
+ */
+bool Worksheet::isOutlineSymbolsVisible() const
+{
+    Q_D(const Worksheet);
+    return d->showOutlineSymbols;
+}
+
+/*!
+ * Show or hide the outline symbols based ib \a visible.
+ */
+void Worksheet::setOutlineSymbolsVisible(bool visible)
+{
+    Q_D(Worksheet);
+    d->showOutlineSymbols = visible;
+}
+
+/*!
+ * Return whether is white space is shown.
+ */
+bool Worksheet::isWhiteSpaceVisible() const
+{
+    Q_D(const Worksheet);
+    return d->showWhiteSpace;
+}
+
+/*!
+ * Show or hide the white space based on \a visible.
+ */
+void Worksheet::setWhiteSpaceVisible(bool visible)
+{
+    Q_D(Worksheet);
+    d->showWhiteSpace = visible;
+}
+
+/*!
+ * Write \a value to cell (\a row, \a column) with the \a format.
+ * Both \a row and \a column are all 1-indexed value.
+ *
+ * Returns true on success.
+ */
+bool Worksheet::write(int row, int column, const QVariant &value, const Format &format)
+{
+    Q_D(Worksheet);
+
+    if (d->checkDimensions(row, column))
+        return false;
+
+    bool ret = true;
+    if (value.isNull()) {
+        //Blank
+        ret = writeBlank(row, column, format);
+    } else if (value.userType() == QMetaType::QString) {
+        //String
+        QString token = value.toString();
+        bool ok;
+
+        if (token.startsWith(QLatin1String("="))) {
+            //convert to formula
+            ret = writeFormula(row, column, CellFormula(token), format);
+        } else if (d->workbook->isStringsToHyperlinksEnabled() && token.contains(d->urlPattern)) {
+            //convert to url
+            ret = writeHyperlink(row, column, QUrl(token));
+        } else if (d->workbook->isStringsToNumbersEnabled() && (value.toDouble(&ok), ok)) {
+            //Try convert string to number if the flag enabled.
+            ret = writeString(row, column, value.toString(), format);
+        } else {
+            //normal string now
+            ret = writeString(row, column, token, format);
+        }
+    } else if (value.userType() == qMetaTypeId<RichString>()) {
+        ret = writeString(row, column, value.value<RichString>(), format);
+    } else if (value.userType() == QMetaType::Int || value.userType() == QMetaType::UInt
+               || value.userType() == QMetaType::LongLong || value.userType() == QMetaType::ULongLong
+               || value.userType() == QMetaType::Double || value.userType() == QMetaType::Float) {
+        //Number
+
+        ret = writeNumeric(row, column, value.toDouble(), format);
+    } else if (value.userType() == QMetaType::Bool) {
+        //Bool
+        ret = writeBool(row,column, value.toBool(), format);
+    } else if (value.userType() == QMetaType::QDateTime || value.userType() == QMetaType::QDate) {
+        //DateTime, Date
+        //  note that, QTime cann't convert to QDateTime
+        ret = writeDateTime(row, column, value.toDateTime(), format);
+    } else if (value.userType() == QMetaType::QTime) {
+        //Time
+        ret = writeTime(row, column, value.toTime(), format);
+    } else if (value.userType() == QMetaType::QUrl) {
+        //Url
+        ret = writeHyperlink(row, column, value.toUrl(), format);
+    } else {
+        //Wrong type
+        return false;
+    }
+
+    return ret;
+}
+
+/*!
+ * \overload
+ * Write \a value to cell \a row_column with the \a format.
+ * Both row and column are all 1-indexed value.
+ * Returns true on success.
+ */
+bool Worksheet::write(const CellReference &row_column, const QVariant &value, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return write(row_column.row(), row_column.column(), value, format);
+}
+
+/*!
+    \overload
+    Return the contents of the cell \a row_column.
+ */
+QVariant Worksheet::read(const CellReference &row_column) const
+{
+    if (!row_column.isValid())
+        return QVariant();
+
+    return read(row_column.row(), row_column.column());
+}
+
+/*!
+    Return the contents of the cell (\a row, \a column).
+ */
+QVariant Worksheet::read(int row, int column) const
+{
+    Q_D(const Worksheet);
+
+    Cell *cell = cellAt(row, column);
+    if (!cell)
+        return QVariant();
+
+    if (cell->hasFormula()) {
+        if (cell->formula().formulaType() == CellFormula::NormalType) {
+            return QVariant(QLatin1String("=")+cell->formula().formulaText());
+        } else if (cell->formula().formulaType() == CellFormula::SharedType) {
+            if (!cell->formula().formulaText().isEmpty()) {
+                return QVariant(QLatin1String("=")+cell->formula().formulaText());
+            } else {
+                const CellFormula &rootFormula = d->sharedFormulaMap[cell->formula().sharedIndex()];
+                CellReference rootCellRef = rootFormula.reference().topLeft();
+                QString rootFormulaText = rootFormula.formulaText();
+                QString newFormulaText = convertSharedFormula(rootFormulaText, rootCellRef, CellReference(row, column));
+                return QVariant(QLatin1String("=")+newFormulaText);
+            }
+        }
+    }
+
+    if (cell->isDateTime()) {
+        double val = cell->value().toDouble();
+        QDateTime dt = cell->dateTime();
+        if (val < 1)
+            return dt.time();
+        if (fmod(val, 1.0) <  1.0/(1000*60*60*24)) //integer
+            return dt.date();
+        return dt;
+    }
+
+    return cell->value();
+}
+
+/*!
+ * Returns the cell at the given \a row_column. If there
+ * is no cell at the specified position, the function returns 0.
+ */
+Cell *Worksheet::cellAt(const CellReference &row_column) const
+{
+    if (!row_column.isValid())
+        return 0;
+
+    return cellAt(row_column.row(), row_column.column());
+}
+
+/*!
+ * Returns the cell at the given \a row and \a column. If there
+ * is no cell at the specified position, the function returns 0.
+ */
+Cell *Worksheet::cellAt(int row, int column) const
+{
+    Q_D(const Worksheet);
+    if (!d->cellTable.contains(row))
+        return 0;
+    if (!d->cellTable[row].contains(column))
+        return 0;
+
+    return d->cellTable[row][column].data();
+}
+
+Format WorksheetPrivate::cellFormat(int row, int col) const
+{
+    if (!cellTable.contains(row))
+        return Format();
+    if (!cellTable[row].contains(col))
+        return Format();
+    return cellTable[row][col]->format();
+}
+
+/*!
+  \overload
+  Write string \a value to the cell \a row_column with the \a format.
+
+  Returns true on success.
+ */
+bool Worksheet::writeString(const CellReference &row_column, const RichString &value, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeString(row_column.row(), row_column.column(), value, format);
+}
+
+/*!
+  Write string \a value to the cell (\a row, \a column) with the \a format.
+  Returns true on success.
+*/
+bool Worksheet::writeString(int row, int column, const RichString &value, const Format &format)
+{
+    Q_D(Worksheet);
+//    QString content = value.toPlainString();
+    if (d->checkDimensions(row, column))
+        return false;
+
+//    if (content.size() > d->xls_strmax) {
+//        content = content.left(d->xls_strmax);
+//        error = -2;
+//    }
+
+    d->sharedStrings()->addSharedString(value);
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    if (value.fragmentCount() == 1 && value.fragmentFormat(0).isValid())
+        fmt.mergeFormat(value.fragmentFormat(0));
+    d->workbook->styles()->addXfFormat(fmt);
+    QSharedPointer<Cell> cell = QSharedPointer<Cell>(new Cell(value.toPlainString(), Cell::SharedStringType, fmt, this));
+    cell->d_ptr->richString = value;
+    d->cellTable[row][column] = cell;
+    return true;
+}
+
+/*!
+    \overload
+    Write string \a value to the cell \a row_column with the \a format.
+ */
+bool Worksheet::writeString(const CellReference &row_column, const QString &value, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeString(row_column.row(), row_column.column(), value, format);
+}
+
+/*!
+    \overload
+
+    Write string \a value to the cell (\a row, \a column) with the \a format.
+    Returns true on success.
+*/
+bool Worksheet::writeString(int row, int column, const QString &value, const Format &format)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    RichString rs;
+    if (d->workbook->isHtmlToRichStringEnabled() && Qt::mightBeRichText(value))
+        rs.setHtml(value);
+    else
+        rs.addFragment(value, Format());
+
+    return writeString(row, column, rs, format);
+}
+
+/*!
+    \overload
+    Write string \a value to the cell \a row_column with the \a format
+ */
+bool Worksheet::writeInlineString(const CellReference &row_column, const QString &value, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeInlineString(row_column.row(), row_column.column(), value, format);
+}
+
+/*!
+    Write string \a value to the cell (\a row, \a column) with the \a format.
+    Returns true on success.
+*/
+bool Worksheet::writeInlineString(int row, int column, const QString &value, const Format &format)
+{
+    Q_D(Worksheet);
+    //int error = 0;
+    QString content = value;
+    if (d->checkDimensions(row, column))
+        return false;
+
+    if (value.size() > XLSX_STRING_MAX) {
+        content = value.left(XLSX_STRING_MAX);
+        //error = -2;
+    }
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    d->workbook->styles()->addXfFormat(fmt);
+    d->cellTable[row][column] = QSharedPointer<Cell>(new Cell(value, Cell::InlineStringType, fmt, this));
+    return true;
+}
+
+/*!
+    \overload
+    Write numeric \a value to the cell \a row_column with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeNumeric(const CellReference &row_column, double value, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeNumeric(row_column.row(), row_column.column(), value, format);
+}
+
+/*!
+    Write numeric \a value to the cell (\a row, \a column) with the \a format.
+    Returns true on success.
+*/
+bool Worksheet::writeNumeric(int row, int column, double value, const Format &format)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    d->workbook->styles()->addXfFormat(fmt);
+    d->cellTable[row][column] = QSharedPointer<Cell>(new Cell(value, Cell::NumberType, fmt, this));
+    return true;
+}
+
+/*!
+    \overload
+    Write \a formula to the cell \a row_column with the \a format and \a result.
+    Returns true on success.
+ */
+bool Worksheet::writeFormula(const CellReference &row_column, const CellFormula &formula, const Format &format, double result)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeFormula(row_column.row(), row_column.column(), formula, format, result);
+}
+
+/*!
+    Write \a formula_ to the cell (\a row, \a column) with the \a format and \a result.
+    Returns true on success.
+*/
+bool Worksheet::writeFormula(int row, int column, const CellFormula &formula_, const Format &format, double result)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    d->workbook->styles()->addXfFormat(fmt);
+
+    CellFormula formula = formula_;
+    formula.d->ca = true;
+    if (formula.formulaType() == CellFormula::SharedType) {
+        //Assign proper shared index for shared formula
+        int si=0;
+        while(d->sharedFormulaMap.contains(si))
+            ++si;
+        formula.d->si = si;
+        d->sharedFormulaMap[si] = formula;
+    }
+
+    QSharedPointer<Cell> data = QSharedPointer<Cell>(new Cell(result, Cell::NumberType, fmt, this));
+    data->d_ptr->formula = formula;
+    d->cellTable[row][column] = data;
+
+    CellRange range = formula.reference();
+    if (formula.formulaType() == CellFormula::SharedType) {
+        CellFormula sf(QString(), CellFormula::SharedType);
+        sf.d->si = formula.sharedIndex();
+        for (int r=range.firstRow(); r<=range.lastRow(); ++r) {
+            for (int c=range.firstColumn(); c<=range.lastColumn(); ++c) {
+                if (!(r==row && c==column)) {
+                    if(Cell *cell = cellAt(r, c)) {
+                        cell->d_ptr->formula = sf;
+                    } else {
+                        QSharedPointer<Cell> newCell = QSharedPointer<Cell>(new Cell(result, Cell::NumberType, fmt, this));
+                        newCell->d_ptr->formula = sf;
+                        d->cellTable[r][c] = newCell;
+                    }
+                }
+            }
+        }
+    } else if (formula.formulaType() == CellFormula::SharedType) {
+
+    }
+
+    return true;
+}
+
+/*!
+    \overload
+    Write a empty cell \a row_column with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeBlank(const CellReference &row_column, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeBlank(row_column.row(), row_column.column(), format);
+}
+
+/*!
+    Write a empty cell (\a row, \a column) with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeBlank(int row, int column, const Format &format)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    d->workbook->styles()->addXfFormat(fmt);
+
+    //Note: NumberType with an invalid QVariant value means blank.
+    d->cellTable[row][column] = QSharedPointer<Cell>(new Cell(QVariant(), Cell::NumberType, fmt, this));
+
+    return true;
+}
+/*!
+    \overload
+    Write a bool \a value to the cell \a row_column with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeBool(const CellReference &row_column, bool value, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeBool(row_column.row(), row_column.column(), value, format);
+}
+
+/*!
+    Write a bool \a value to the cell (\a row, \a column) with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeBool(int row, int column, bool value, const Format &format)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    d->workbook->styles()->addXfFormat(fmt);
+    d->cellTable[row][column] = QSharedPointer<Cell>(new Cell(value, Cell::BooleanType, fmt, this));
+
+    return true;
+}
+/*!
+    \overload
+    Write a QDateTime \a dt to the cell \a row_column with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeDateTime(const CellReference &row_column, const QDateTime &dt, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeDateTime(row_column.row(), row_column.column(), dt, format);
+}
+
+/*!
+    Write a QDateTime \a dt to the cell (\a row, \a column) with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeDateTime(int row, int column, const QDateTime &dt, const Format &format)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    if (!fmt.isValid() || !fmt.isDateTimeFormat())
+        fmt.setNumberFormat(d->workbook->defaultDateFormat());
+    d->workbook->styles()->addXfFormat(fmt);
+
+    double value = datetimeToNumber(dt, d->workbook->isDate1904());
+
+    d->cellTable[row][column] = QSharedPointer<Cell>(new Cell(value, Cell::NumberType, fmt, this));
+
+    return true;
+}
+
+/*!
+    \overload
+    Write a QTime \a t to the cell \a row_column with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeTime(const CellReference &row_column, const QTime &t, const Format &format)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeTime(row_column.row(), row_column.column(), t, format);
+}
+
+/*!
+    Write a QTime \a t to the cell (\a row, \a column) with the \a format.
+    Returns true on success.
+ */
+bool Worksheet::writeTime(int row, int column, const QTime &t, const Format &format)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    if (!fmt.isValid() || !fmt.isDateTimeFormat())
+        fmt.setNumberFormat(QStringLiteral("hh:mm:ss"));
+    d->workbook->styles()->addXfFormat(fmt);
+
+    d->cellTable[row][column] = QSharedPointer<Cell>(new Cell(timeToNumber(t), Cell::NumberType, fmt, this));
+
+    return true;
+}
+
+/*!
+    \overload
+    Write a QUrl \a url to the cell \a row_column with the given \a format \a display and \a tip.
+    Returns true on success.
+ */
+bool Worksheet::writeHyperlink(const CellReference &row_column, const QUrl &url, const Format &format, const QString &display, const QString &tip)
+{
+    if (!row_column.isValid())
+        return false;
+
+    return writeHyperlink(row_column.row(), row_column.column(), url, format, display, tip);
+}
+
+/*!
+    Write a QUrl \a url to the cell (\a row, \a column) with the given \a format \a display and \a tip.
+    Returns true on success.
+ */
+bool Worksheet::writeHyperlink(int row, int column, const QUrl &url, const Format &format, const QString &display, const QString &tip)
+{
+    Q_D(Worksheet);
+    if (d->checkDimensions(row, column))
+        return false;
+
+    //int error = 0;
+
+    QString urlString = url.toString();
+
+    //Generate proper display string
+    QString displayString = display.isEmpty() ? urlString : display;
+    if (displayString.startsWith(QLatin1String("mailto:")))
+        displayString.replace(QLatin1String("mailto:"), QString());
+    if (displayString.size() > XLSX_STRING_MAX) {
+        displayString = displayString.left(XLSX_STRING_MAX);
+        //error = -2;
+    }
+
+    /*
+      Location within target. If target is a workbook (or this workbook)
+      this shall refer to a sheet and cell or a defined name. Can also
+      be an HTML anchor if target is HTML file.
+
+      c:\temp\file.xlsx#Sheet!A1
+      http://a.com/aaa.html#aaaaa
+    */
+    QString locationString;
+    if (url.hasFragment()) {
+        locationString = url.fragment();
+        urlString = url.toString(QUrl::RemoveFragment);
+    }
+
+    Format fmt = format.isValid() ? format : d->cellFormat(row, column);
+    //Given a default style for hyperlink
+    if (!fmt.isValid()) {
+        fmt.setFontColor(Qt::blue);
+        fmt.setFontUnderline(Format::FontUnderlineSingle);
+    }
+    d->workbook->styles()->addXfFormat(fmt);
+
+    //Write the hyperlink string as normal string.
+    d->sharedStrings()->addSharedString(displayString);
+    d->cellTable[row][column] = QSharedPointer<Cell>(new Cell(displayString, Cell::SharedStringType, fmt, this));
+
+    //Store the hyperlink data in a separate table
+    d->urlTable[row][column] = QSharedPointer<XlsxHyperlinkData>(new XlsxHyperlinkData(XlsxHyperlinkData::External, urlString, locationString, QString(), tip));
+
+    return true;
+}
+
+/*!
+ * Add one DataValidation \a validation to the sheet.
+ * Returns true on success.
+ */
+bool Worksheet::addDataValidation(const DataValidation &validation)
+{
+    Q_D(Worksheet);
+    if (validation.ranges().isEmpty() || validation.validationType()==DataValidation::None)
+        return false;
+
+    d->dataValidationsList.append(validation);
+    return true;
+}
+
+/*!
+ * Add one ConditionalFormatting \a cf to the sheet.
+ * Returns true on success.
+ */
+bool Worksheet::addConditionalFormatting(const ConditionalFormatting &cf)
+{
+    Q_D(Worksheet);
+    if (cf.ranges().isEmpty())
+        return false;
+
+    for (int i=0; i<cf.d->cfRules.size(); ++i) {
+        const QSharedPointer<XlsxCfRuleData> &rule = cf.d->cfRules[i];
+        if (!rule->dxfFormat.isEmpty())
+            d->workbook->styles()->addDxfFormat(rule->dxfFormat);
+        rule->priority = 1;
+    }
+    d->conditionalFormattingList.append(cf);
+    return true;
+}
+
+/*!
+ * Insert an \a image  at the position \a row, \a column
+ * Returns true on success.
+ */
+bool Worksheet::insertImage(int row, int column, const QImage &image)
+{
+    Q_D(Worksheet);
+
+    if (image.isNull())
+        return false;
+
+    if (!d->drawing)
+        d->drawing = QSharedPointer<Drawing>(new Drawing(this, F_NewFromScratch));
+
+    DrawingOneCellAnchor *anchor = new DrawingOneCellAnchor(d->drawing.data(), DrawingAnchor::Picture);
+
+    /*
+        The size are expressed as English Metric Units (EMUs). There are
+        12,700 EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per
+        pixel
+    */
+    anchor->from = XlsxMarker(row, column, 0, 0);
+    anchor->ext = QSize(image.width() * 9525, image.height() * 9525);
+
+    anchor->setObjectPicture(image);
+    return true;
+}
+
+/*!
+ * Creates an chart with the given \a size and insert
+ * at the position \a row, \a column.
+ * The chart will be returned.
+ */
+Chart *Worksheet::insertChart(int row, int column, const QSize &size)
+{
+    Q_D(Worksheet);
+
+    if (!d->drawing)
+        d->drawing = QSharedPointer<Drawing>(new Drawing(this, F_NewFromScratch));
+
+    DrawingOneCellAnchor *anchor = new DrawingOneCellAnchor(d->drawing.data(), DrawingAnchor::Picture);
+
+    /*
+        The size are expressed as English Metric Units (EMUs). There are
+        12,700 EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per
+        pixel
+    */
+    anchor->from = XlsxMarker(row, column, 0, 0);
+    anchor->ext = size * 9525;
+
+    QSharedPointer<Chart> chart = QSharedPointer<Chart>(new Chart(this, F_NewFromScratch));
+    anchor->setObjectGraphicFrame(chart);
+
+    return chart.data();
+}
+
+/*!
+    Merge a \a range of cells. The first cell should contain the data and the others should
+    be blank. All cells will be applied the same style if a valid \a format is given.
+    Returns true on success.
+
+    \note All cells except the top-left one will be cleared.
+ */
+bool Worksheet::mergeCells(const CellRange &range, const Format &format)
+{
+    Q_D(Worksheet);
+    if (range.rowCount() < 2 && range.columnCount() < 2)
+        return false;
+
+    if (d->checkDimensions(range.firstRow(), range.firstColumn()))
+        return false;
+
+    if (format.isValid())
+        d->workbook->styles()->addXfFormat(format);
+
+    for (int row = range.firstRow(); row <= range.lastRow(); ++row) {
+        for (int col = range.firstColumn(); col <= range.lastColumn(); ++col) {
+            if (row == range.firstRow() && col == range.firstColumn()) {
+                Cell *cell = cellAt(row, col);
+                if (cell) {
+                    if (format.isValid())
+                        cell->d_ptr->format = format;
+                } else {
+                    writeBlank(row, col, format);
+                }
+            } else {
+                writeBlank(row, col, format);
+            }
+        }
+    }
+
+    d->merges.append(range);
+    return true;
+}
+
+/*!
+    Unmerge the cells in the \a range. Returns true on success.
+
+*/
+bool Worksheet::unmergeCells(const CellRange &range)
+{
+    Q_D(Worksheet);
+    if (!d->merges.contains(range))
+        return false;
+
+    d->merges.removeOne(range);
+    return true;
+}
+
+/*!
+  Returns all the merged cells.
+*/
+QList<CellRange> Worksheet::mergedCells() const
+{
+    Q_D(const Worksheet);
+    return d->merges;
+}
+
+/*!
+ * \internal
+ */
+void Worksheet::saveToXmlFile(QIODevice *device) const
+{
+    Q_D(const Worksheet);
+    d->relationships->clear();
+
+    QXmlStreamWriter writer(device);
+
+    writer.writeStartDocument(QStringLiteral("1.0"), true);
+    writer.writeStartElement(QStringLiteral("worksheet"));
+    writer.writeAttribute(QStringLiteral("xmlns"), QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main"));
+    writer.writeAttribute(QStringLiteral("xmlns:r"), QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships"));
+
+    //for Excel 2010
+    //    writer.writeAttribute("xmlns:mc", "http://schemas.openxmlformats.org/markup-compatibility/2006");
+    //    writer.writeAttribute("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac");
+    //    writer.writeAttribute("mc:Ignorable", "x14ac");
+
+    writer.writeStartElement(QStringLiteral("dimension"));
+    writer.writeAttribute(QStringLiteral("ref"), d->generateDimensionString());
+    writer.writeEndElement();//dimension
+
+    writer.writeStartElement(QStringLiteral("sheetViews"));
+    writer.writeStartElement(QStringLiteral("sheetView"));
+    if (d->windowProtection)
+        writer.writeAttribute(QStringLiteral("windowProtection"), QStringLiteral("1"));
+    if (d->showFormulas)
+        writer.writeAttribute(QStringLiteral("showFormulas"), QStringLiteral("1"));
+    if (!d->showGridLines)
+        writer.writeAttribute(QStringLiteral("showGridLines"), QStringLiteral("0"));
+    if (!d->showRowColHeaders)
+        writer.writeAttribute(QStringLiteral("showRowColHeaders"), QStringLiteral("0"));
+    if (!d->showZeros)
+        writer.writeAttribute(QStringLiteral("showZeros"), QStringLiteral("0"));
+    if (d->rightToLeft)
+        writer.writeAttribute(QStringLiteral("rightToLeft"), QStringLiteral("1"));
+    if (d->tabSelected)
+        writer.writeAttribute(QStringLiteral("tabSelected"), QStringLiteral("1"));
+    if (!d->showRuler)
+        writer.writeAttribute(QStringLiteral("showRuler"), QStringLiteral("0"));
+    if (!d->showOutlineSymbols)
+        writer.writeAttribute(QStringLiteral("showOutlineSymbols"), QStringLiteral("0"));
+    if (!d->showWhiteSpace)
+        writer.writeAttribute(QStringLiteral("showWhiteSpace"), QStringLiteral("0"));
+    writer.writeAttribute(QStringLiteral("workbookViewId"), QStringLiteral("0"));
+    writer.writeEndElement();//sheetView
+    writer.writeEndElement();//sheetViews
+
+    writer.writeStartElement(QStringLiteral("sheetFormatPr"));
+    writer.writeAttribute(QStringLiteral("defaultRowHeight"), QString::number(d->default_row_height));
+    if (d->default_row_height != 15)
+        writer.writeAttribute(QStringLiteral("customHeight"), QStringLiteral("1"));
+    if (d->default_row_zeroed)
+        writer.writeAttribute(QStringLiteral("zeroHeight"), QStringLiteral("1"));
+    if (d->outline_row_level)
+        writer.writeAttribute(QStringLiteral("outlineLevelRow"), QString::number(d->outline_row_level));
+    if (d->outline_col_level)
+        writer.writeAttribute(QStringLiteral("outlineLevelCol"), QString::number(d->outline_col_level));
+    //for Excel 2010
+    //    writer.writeAttribute("x14ac:dyDescent", "0.25");
+    writer.writeEndElement();//sheetFormatPr
+
+    if (!d->colsInfo.isEmpty()) {
+        writer.writeStartElement(QStringLiteral("cols"));
+        QMapIterator<int, QSharedPointer<XlsxColumnInfo> > it(d->colsInfo);
+        while (it.hasNext()) {
+            it.next();
+            QSharedPointer<XlsxColumnInfo> col_info = it.value();
+            writer.writeStartElement(QStringLiteral("col"));
+            writer.writeAttribute(QStringLiteral("min"), QString::number(col_info->firstColumn));
+            writer.writeAttribute(QStringLiteral("max"), QString::number(col_info->lastColumn));
+            if (col_info->width)
+                writer.writeAttribute(QStringLiteral("width"), QString::number(col_info->width, 'g', 15));
+            if (!col_info->format.isEmpty())
+                writer.writeAttribute(QStringLiteral("style"), QString::number(col_info->format.xfIndex()));
+            if (col_info->hidden)
+                writer.writeAttribute(QStringLiteral("hidden"), QStringLiteral("1"));
+            if (col_info->width)
+                writer.writeAttribute(QStringLiteral("customWidth"), QStringLiteral("1"));
+            if (col_info->outlineLevel)
+                writer.writeAttribute(QStringLiteral("outlineLevel"), QString::number(col_info->outlineLevel));
+            if (col_info->collapsed)
+                writer.writeAttribute(QStringLiteral("collapsed"), QStringLiteral("1"));
+            writer.writeEndElement();//col
+        }
+        writer.writeEndElement();//cols
+    }
+
+    writer.writeStartElement(QStringLiteral("sheetData"));
+    if (d->dimension.isValid())
+        d->saveXmlSheetData(writer);
+    writer.writeEndElement();//sheetData
+
+    d->saveXmlMergeCells(writer);
+    foreach (const ConditionalFormatting cf, d->conditionalFormattingList)
+        cf.saveToXml(writer);
+    d->saveXmlDataValidations(writer);
+    d->saveXmlHyperlinks(writer);
+    d->saveXmlDrawings(writer);
+
+    writer.writeEndElement();//worksheet
+    writer.writeEndDocument();
+}
+
+void WorksheetPrivate::saveXmlSheetData(QXmlStreamWriter &writer) const
+{
+    calculateSpans();
+    for (int row_num = dimension.firstRow(); row_num <= dimension.lastRow(); row_num++) {
+        if (!(cellTable.contains(row_num) || comments.contains(row_num) || rowsInfo.contains(row_num))) {
+            //Only process rows with cell data / comments / formatting
+            continue;
+        }
+
+        int span_index = (row_num-1) / 16;
+        QString span;
+        if (row_spans.contains(span_index))
+            span = row_spans[span_index];
+
+        writer.writeStartElement(QStringLiteral("row"));
+        writer.writeAttribute(QStringLiteral("r"), QString::number(row_num));
+
+        if (!span.isEmpty())
+            writer.writeAttribute(QStringLiteral("spans"), span);
+
+        if (rowsInfo.contains(row_num)) {
+            QSharedPointer<XlsxRowInfo> rowInfo = rowsInfo[row_num];
+            if (!rowInfo->format.isEmpty()) {
+                writer.writeAttribute(QStringLiteral("s"), QString::number(rowInfo->format.xfIndex()));
+                writer.writeAttribute(QStringLiteral("customFormat"), QStringLiteral("1"));
+            }
+            //!Todo: support customHeight from info struct
+            //!Todo: where does this magic number '15' come from?
+            if (rowInfo->customHeight) {
+                writer.writeAttribute(QStringLiteral("ht"), QString::number(rowInfo->height));
+                writer.writeAttribute(QStringLiteral("customHeight"), QStringLiteral("1"));
+            } else {
+                writer.writeAttribute(QStringLiteral("customHeight"), QStringLiteral("0"));
+            }
+
+            if (rowInfo->hidden)
+                writer.writeAttribute(QStringLiteral("hidden"), QStringLiteral("1"));
+            if (rowInfo->outlineLevel > 0)
+                writer.writeAttribute(QStringLiteral("outlineLevel"), QString::number(rowInfo->outlineLevel));
+            if (rowInfo->collapsed)
+                writer.writeAttribute(QStringLiteral("collapsed"), QStringLiteral("1"));
+        }
+
+        //Write cell data if row contains filled cells
+        if (cellTable.contains(row_num)) {
+            for (int col_num = dimension.firstColumn(); col_num <= dimension.lastColumn(); col_num++) {
+                if (cellTable[row_num].contains(col_num)) {
+                    saveXmlCellData(writer, row_num, col_num, cellTable[row_num][col_num]);
+                }
+            }
+        }
+        writer.writeEndElement(); //row
+    }
+}
+
+void WorksheetPrivate::saveXmlCellData(QXmlStreamWriter &writer, int row, int col, QSharedPointer<Cell> cell) const
+{
+    //This is the innermost loop so efficiency is important.
+    QString cell_pos = CellReference(row, col).toString();
+
+    writer.writeStartElement(QStringLiteral("c"));
+    writer.writeAttribute(QStringLiteral("r"), cell_pos);
+
+    //Style used by the cell, row or col
+    if (!cell->format().isEmpty())
+        writer.writeAttribute(QStringLiteral("s"), QString::number(cell->format().xfIndex()));
+    else if (rowsInfo.contains(row) && !rowsInfo[row]->format.isEmpty())
+        writer.writeAttribute(QStringLiteral("s"), QString::number(rowsInfo[row]->format.xfIndex()));
+    else if (colsInfoHelper.contains(col) && !colsInfoHelper[col]->format.isEmpty())
+        writer.writeAttribute(QStringLiteral("s"), QString::number(colsInfoHelper[col]->format.xfIndex()));
+
+    if (cell->cellType() == Cell::SharedStringType) {
+        int sst_idx;
+        if (cell->isRichString())
+            sst_idx = sharedStrings()->getSharedStringIndex(cell->d_ptr->richString);
+        else
+            sst_idx = sharedStrings()->getSharedStringIndex(cell->value().toString());
+
+        writer.writeAttribute(QStringLiteral("t"), QStringLiteral("s"));
+        writer.writeTextElement(QStringLiteral("v"), QString::number(sst_idx));
+    } else if (cell->cellType() == Cell::InlineStringType) {
+        writer.writeAttribute(QStringLiteral("t"), QStringLiteral("inlineStr"));
+        writer.writeStartElement(QStringLiteral("is"));
+        if (cell->isRichString()) {
+            //Rich text string
+            RichString string = cell->d_ptr->richString;
+            for (int i=0; i<string.fragmentCount(); ++i) {
+                writer.writeStartElement(QStringLiteral("r"));
+                if (string.fragmentFormat(i).hasFontData()) {
+                    writer.writeStartElement(QStringLiteral("rPr"));
+                    //:Todo
+                    writer.writeEndElement();// rPr
+                }
+                writer.writeStartElement(QStringLiteral("t"));
+                if (isSpaceReserveNeeded(string.fragmentText(i)))
+                    writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
+                writer.writeCharacters(string.fragmentText(i));
+                writer.writeEndElement();// t
+                writer.writeEndElement(); // r
+            }
+        } else {
+            writer.writeStartElement(QStringLiteral("t"));
+            QString string = cell->value().toString();
+            if (isSpaceReserveNeeded(string))
+                writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
+            writer.writeCharacters(string);
+            writer.writeEndElement(); // t
+        }
+        writer.writeEndElement();//is
+    } else if (cell->cellType() == Cell::NumberType){
+        if (cell->hasFormula())
+            cell->formula().saveToXml(writer);
+        if (cell->value().isValid()) {//note that, invalid value means 'v' is blank
+            double value = cell->value().toDouble();
+            writer.writeTextElement(QStringLiteral("v"), QString::number(value, 'g', 15));
+        }
+    } else if (cell->cellType() == Cell::StringType) {
+        writer.writeAttribute(QStringLiteral("t"), QStringLiteral("str"));
+        if (cell->hasFormula())
+            cell->formula().saveToXml(writer);
+        writer.writeTextElement(QStringLiteral("v"), cell->value().toString());
+    } else if (cell->cellType() == Cell::BooleanType) {
+        writer.writeAttribute(QStringLiteral("t"), QStringLiteral("b"));
+        writer.writeTextElement(QStringLiteral("v"), cell->value().toBool() ? QStringLiteral("1") : QStringLiteral("0"));
+    }
+    writer.writeEndElement(); //c
+}
+
+void WorksheetPrivate::saveXmlMergeCells(QXmlStreamWriter &writer) const
+{
+    if (merges.isEmpty())
+        return;
+
+    writer.writeStartElement(QStringLiteral("mergeCells"));
+    writer.writeAttribute(QStringLiteral("count"), QString::number(merges.size()));
+
+    foreach (CellRange range, merges) {
+        writer.writeEmptyElement(QStringLiteral("mergeCell"));
+        writer.writeAttribute(QStringLiteral("ref"), range.toString());
+    }
+
+    writer.writeEndElement(); //mergeCells
+}
+
+void WorksheetPrivate::saveXmlDataValidations(QXmlStreamWriter &writer) const
+{
+    if (dataValidationsList.isEmpty())
+        return;
+
+    writer.writeStartElement(QStringLiteral("dataValidations"));
+    writer.writeAttribute(QStringLiteral("count"), QString::number(dataValidationsList.size()));
+
+    foreach (DataValidation validation, dataValidationsList)
+        validation.saveToXml(writer);
+
+    writer.writeEndElement(); //dataValidations
+}
+
+void WorksheetPrivate::saveXmlHyperlinks(QXmlStreamWriter &writer) const
+{
+    if (urlTable.isEmpty())
+        return;
+
+    writer.writeStartElement(QStringLiteral("hyperlinks"));
+    QMapIterator<int, QMap<int, QSharedPointer<XlsxHyperlinkData> > > it(urlTable);
+    while (it.hasNext()) {
+        it.next();
+        int row = it.key();
+        QMapIterator <int, QSharedPointer<XlsxHyperlinkData> > it2(it.value());
+        while (it2.hasNext()) {
+            it2.next();
+            int col = it2.key();
+            QSharedPointer<XlsxHyperlinkData> data = it2.value();
+            QString ref = CellReference(row, col).toString();
+            writer.writeEmptyElement(QStringLiteral("hyperlink"));
+            writer.writeAttribute(QStringLiteral("ref"), ref);
+            if (data->linkType == XlsxHyperlinkData::External) {
+                //Update relationships
+                relationships->addWorksheetRelationship(QStringLiteral("/hyperlink"), data->target, QStringLiteral("External"));
+
+                writer.writeAttribute(QStringLiteral("r:id"), QStringLiteral("rId%1").arg(relationships->count()));
+            }
+
+            if (!data->location.isEmpty())
+                writer.writeAttribute(QStringLiteral("location"), data->location);
+            if (!data->display.isEmpty())
+                writer.writeAttribute(QStringLiteral("display"), data->display);
+            if (!data->tooltip.isEmpty())
+                writer.writeAttribute(QStringLiteral("tooltip"), data->tooltip);
+        }
+    }
+
+    writer.writeEndElement();//hyperlinks
+}
+
+void WorksheetPrivate::saveXmlDrawings(QXmlStreamWriter &writer) const
+{
+    if (!drawing)
+        return;
+
+    int idx = workbook->drawings().indexOf(drawing.data());
+    relationships->addWorksheetRelationship(QStringLiteral("/drawing"), QStringLiteral("../drawings/drawing%1.xml").arg(idx+1));
+
+    writer.writeEmptyElement(QStringLiteral("drawing"));
+    writer.writeAttribute(QStringLiteral("r:id"), QStringLiteral("rId%1").arg(relationships->count()));
+}
+
+void WorksheetPrivate::splitColsInfo(int colFirst, int colLast)
+{
+    // Split current columnInfo, for example, if "A:H" has been set,
+    // we are trying to set "B:D", there should be "A", "B:D", "E:H".
+    // This will be more complex if we try to set "C:F" after "B:D".
+    {
+        QMapIterator<int, QSharedPointer<XlsxColumnInfo> > it(colsInfo);
+        while (it.hasNext()) {
+            it.next();
+            QSharedPointer<XlsxColumnInfo> info = it.value();
+            if (colFirst > info->firstColumn && colFirst <= info->lastColumn) {
+                //split the range,
+                QSharedPointer<XlsxColumnInfo> info2(new XlsxColumnInfo(*info));
+                info->lastColumn = colFirst - 1;
+                info2->firstColumn = colFirst;
+                colsInfo.insert(colFirst, info2);
+                for (int c = info2->firstColumn; c <= info2->lastColumn; ++c)
+                    colsInfoHelper[c] = info2;
+
+                break;
+            }
+        }
+    }
+    {
+        QMapIterator<int, QSharedPointer<XlsxColumnInfo> > it(colsInfo);
+        while (it.hasNext()) {
+            it.next();
+            QSharedPointer<XlsxColumnInfo> info = it.value();
+            if (colLast >= info->firstColumn && colLast < info->lastColumn) {
+                QSharedPointer<XlsxColumnInfo> info2(new XlsxColumnInfo(*info));
+                info->lastColumn = colLast;
+                info2->firstColumn = colLast + 1;
+                colsInfo.insert(colLast + 1, info2);
+                for (int c = info2->firstColumn; c <= info2->lastColumn; ++c)
+                    colsInfoHelper[c] = info2;
+
+                break;
+            }
+        }
+    }
+}
+
+bool WorksheetPrivate::isColumnRangeValid(int colFirst, int colLast)
+{
+    bool ignore_row = true;
+    bool ignore_col = false;
+
+    if (colFirst > colLast)
+        return false;
+
+    if (checkDimensions(1, colLast, ignore_row, ignore_col))
+        return false;
+    if (checkDimensions(1, colFirst, ignore_row, ignore_col))
+        return false;
+
+    return true;
+}
+
+QList<int> WorksheetPrivate ::getColumnIndexes(int colFirst, int colLast)
+{
+    splitColsInfo(colFirst, colLast);
+
+    QList<int> nodes;
+    nodes.append(colFirst);
+    for (int col = colFirst; col <= colLast; ++col) {
+        if (colsInfo.contains(col)) {
+            if (nodes.last() != col)
+                nodes.append(col);
+            int nextCol = colsInfo[col]->lastColumn + 1;
+            if (nextCol <= colLast)
+                nodes.append(nextCol);
+        }
+    }
+
+    return nodes;
+}
+
+/*!
+  Sets width in characters of a \a range of columns to \a width.
+  Returns true on success.
+ */
+bool Worksheet::setColumnWidth(const CellRange &range, double width)
+{
+    if (!range.isValid())
+        return false;
+
+    return setColumnWidth(range.firstColumn(), range.lastColumn(), width);
+}
+
+/*!
+  Sets format property of a \a range of columns to \a format. Columns are 1-indexed.
+  Returns true on success.
+ */
+bool Worksheet::setColumnFormat(const CellRange& range, const Format &format)
+{
+    if (!range.isValid())
+        return false;
+
+    return setColumnFormat(range.firstColumn(), range.lastColumn(), format);
+}
+
+/*!
+  Sets hidden property of a \a range of columns to \a hidden. Columns are 1-indexed.
+  Hidden columns are not visible.
+  Returns true on success.
+ */
+bool Worksheet::setColumnHidden(const CellRange &range, bool hidden)
+{
+    if (!range.isValid())
+        return false;
+
+    return setColumnHidden(range.firstColumn(), range.lastColumn(), hidden);
+}
+
+/*!
+  Sets width in characters for columns [\a colFirst, \a colLast] to \a width.
+  Columns are 1-indexed.
+  Returns true on success.
+ */
+bool Worksheet::setColumnWidth(int colFirst, int colLast, double width)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxColumnInfo> > columnInfoList = d->getColumnInfoList(colFirst, colLast);
+    foreach(QSharedPointer<XlsxColumnInfo>  columnInfo, columnInfoList)
+       columnInfo->width = width;
+
+    return (columnInfoList.count() > 0);
+}
+
+/*!
+  Sets format property of a range of columns [\a colFirst, \a colLast] to \a format.
+  Columns are 1-indexed.
+  Returns true on success.
+ */
+bool Worksheet::setColumnFormat(int colFirst, int colLast, const Format &format)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxColumnInfo> > columnInfoList = d->getColumnInfoList(colFirst, colLast);
+    foreach(QSharedPointer<XlsxColumnInfo>  columnInfo, columnInfoList)
+       columnInfo->format = format;
+
+    if(columnInfoList.count() > 0) {
+       d->workbook->styles()->addXfFormat(format);
+       return true;
+    }
+
+    return false;
+}
+
+/*!
+  Sets hidden property of a range of columns [\a colFirst, \a colLast] to \a hidden.
+  Columns are 1-indexed. Returns true on success.
+ */
+bool Worksheet::setColumnHidden(int colFirst, int colLast, bool hidden)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxColumnInfo> > columnInfoList = d->getColumnInfoList(colFirst, colLast);
+    foreach(QSharedPointer<XlsxColumnInfo>  columnInfo, columnInfoList)
+       columnInfo->hidden = hidden;
+
+    return (columnInfoList.count() > 0);
+}
+
+/*!
+  Returns width of the \a column in characters of the normal font. Columns are 1-indexed.
+ */
+double Worksheet::columnWidth(int column)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxColumnInfo> > columnInfoList = d->getColumnInfoList(column, column);
+    if (columnInfoList.count() == 1)
+       return columnInfoList.at(0)->width ;
+
+    return d->sheetFormatProps.defaultColWidth;
+}
+
+/*!
+  Returns formatting of the \a column. Columns are 1-indexed.
+ */
+Format Worksheet::columnFormat(int column)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxColumnInfo> > columnInfoList = d->getColumnInfoList(column, column);
+    if (columnInfoList.count() == 1)
+       return columnInfoList.at(0)->format;
+
+    return Format();
+}
+
+/*!
+  Returns true if \a column is hidden. Columns are 1-indexed.
+ */
+bool Worksheet::isColumnHidden(int column)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxColumnInfo> > columnInfoList = d->getColumnInfoList(column, column);
+    if (columnInfoList.count() == 1)
+       return columnInfoList.at(0)->hidden;
+
+    return false;
+}
+
+/*!
+  Sets the \a height of the rows including and between \a rowFirst and \a rowLast.
+  Row height measured in point size.
+  Rows are 1-indexed.
+
+  Returns true if success.
+*/
+bool Worksheet::setRowHeight(int rowFirst,int rowLast, double height)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxRowInfo> > rowInfoList = d->getRowInfoList(rowFirst,rowLast);
+
+    foreach(QSharedPointer<XlsxRowInfo> rowInfo, rowInfoList) {
+        rowInfo->height = height;
+        rowInfo->customHeight = true;
+    }
+
+    return rowInfoList.count() > 0;
+}
+
+/*!
+  Sets the \a format of the rows including and between \a rowFirst and \a rowLast.
+  Rows are 1-indexed.
+
+  Returns true if success.
+*/
+bool Worksheet::setRowFormat(int rowFirst,int rowLast, const Format &format)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxRowInfo> > rowInfoList = d->getRowInfoList(rowFirst,rowLast);
+
+    foreach(QSharedPointer<XlsxRowInfo> rowInfo, rowInfoList)
+        rowInfo->format = format;
+
+    d->workbook->styles()->addXfFormat(format);
+    return rowInfoList.count() > 0;
+}
+
+/*!
+  Sets the \a hidden proeprty of the rows including and between \a rowFirst and \a rowLast.
+  Rows are 1-indexed. If hidden is true rows will not be visible.
+
+  Returns true if success.
+*/
+bool Worksheet::setRowHidden(int rowFirst,int rowLast, bool hidden)
+{
+    Q_D(Worksheet);
+
+    QList <QSharedPointer<XlsxRowInfo> > rowInfoList = d->getRowInfoList(rowFirst,rowLast);
+    foreach(QSharedPointer<XlsxRowInfo> rowInfo, rowInfoList)
+        rowInfo->hidden = hidden;
+
+    return rowInfoList.count() > 0;
+}
+
+/*!
+ Returns height of \a row in points.
+*/
+double Worksheet::rowHeight(int row)
+{
+    Q_D(Worksheet);
+    int min_col = d->dimension.isValid() ? d->dimension.firstColumn() : 1;
+
+    if (d->checkDimensions(row, min_col, false, true) || !d->rowsInfo.contains(row))
+        return d->sheetFormatProps.defaultRowHeight; //return default on invalid row
+
+
+    return d->rowsInfo[row]->height;
+}
+
+/*!
+ Returns format of \a row.
+*/
+Format Worksheet::rowFormat(int row)
+{
+    Q_D(Worksheet);
+    int min_col = d->dimension.isValid() ? d->dimension.firstColumn() : 1;
+    if (d->checkDimensions(row, min_col, false, true) || !d->rowsInfo.contains(row))
+        return Format(); //return default on invalid row
+
+    return d->rowsInfo[row]->format;
+}
+
+/*!
+ Returns true if \a row is hidden.
+*/
+bool Worksheet::isRowHidden(int row)
+{
+    Q_D(Worksheet);
+    int min_col = d->dimension.isValid() ? d->dimension.firstColumn() : 1;
+    if (d->checkDimensions(row, min_col, false, true) || !d->rowsInfo.contains(row))
+        return false; //return default on invalid row
+
+    return d->rowsInfo[row]->hidden;
+}
+
+/*!
+   Groups rows from \a rowFirst to \a rowLast with the given \a collapsed.
+
+   Returns false if error occurs.
+ */
+bool Worksheet::groupRows(int rowFirst, int rowLast, bool collapsed)
+{
+    Q_D(Worksheet);
+
+    for (int row=rowFirst; row<=rowLast; ++row) {
+        if (d->rowsInfo.contains(row)) {
+            d->rowsInfo[row]->outlineLevel += 1;
+        } else {
+            QSharedPointer<XlsxRowInfo> info(new XlsxRowInfo);
+            info->outlineLevel += 1;
+            d->rowsInfo.insert(row, info);
+        }
+        if (collapsed)
+            d->rowsInfo[row]->hidden = true;
+    }
+    if (collapsed) {
+        if (!d->rowsInfo.contains(rowLast+1))
+            d->rowsInfo.insert(rowLast+1, QSharedPointer<XlsxRowInfo>(new XlsxRowInfo));
+        d->rowsInfo[rowLast+1]->collapsed = true;
+    }
+    return true;
+}
+
+/*!
+    \overload
+
+    Groups columns with the given \a range and \a collapsed.
+ */
+bool Worksheet::groupColumns(const CellRange &range, bool collapsed)
+{
+    if (!range.isValid())
+        return false;
+
+    return groupColumns(range.firstColumn(), range.lastColumn(), collapsed);
+}
+
+/*!
+   Groups columns from \a colFirst to \a colLast with the given \a collapsed.
+   Returns false if error occurs.
+*/
+bool Worksheet::groupColumns(int colFirst, int colLast, bool collapsed)
+{
+    Q_D(Worksheet);
+
+    d->splitColsInfo(colFirst, colLast);
+
+    QList<int> nodes;
+    nodes.append(colFirst);
+    for (int col = colFirst; col <= colLast; ++col) {
+        if (d->colsInfo.contains(col)) {
+            if (nodes.last() != col)
+                nodes.append(col);
+            int nextCol = d->colsInfo[col]->lastColumn + 1;
+            if (nextCol <= colLast)
+                nodes.append(nextCol);
+        }
+    }
+
+    for (int idx = 0; idx < nodes.size(); ++idx) {
+        int colStart = nodes[idx];
+        if (d->colsInfo.contains(colStart)) {
+            QSharedPointer<XlsxColumnInfo> info = d->colsInfo[colStart];
+            info->outlineLevel += 1;
+            if (collapsed)
+                info->hidden = true;
+        } else {
+            int colEnd = (idx == nodes.size() - 1) ? colLast : nodes[idx+1] - 1;
+            QSharedPointer<XlsxColumnInfo> info(new XlsxColumnInfo(colStart, colEnd));
+            info->outlineLevel += 1;
+            d->colsInfo.insert(colFirst, info);
+            if (collapsed)
+                info->hidden = true;
+            for (int c = colStart; c <= colEnd; ++c)
+                d->colsInfoHelper[c] = info;
+        }
+    }
+
+    if (collapsed) {
+        int col = colLast+1;
+        d->splitColsInfo(col, col);
+        if (d->colsInfo.contains(col))
+            d->colsInfo[col]->collapsed = true;
+        else {
+            QSharedPointer<XlsxColumnInfo> info(new XlsxColumnInfo(col, col));
+            info->collapsed = true;
+            d->colsInfo.insert(col, info);
+            d->colsInfoHelper[col] = info;
+        }
+    }
+
+    return false;
+}
+
+/*!
+    Return the range that contains cell data.
+ */
+CellRange Worksheet::dimension() const
+{
+    Q_D(const Worksheet);
+    return d->dimension;
+}
+
+/*
+ Convert the height of a cell from user's units to pixels. If the
+ height hasn't been set by the user we use the default value. If
+ the row is hidden it has a value of zero.
+*/
+int WorksheetPrivate::rowPixelsSize(int row) const
+{
+    double height;
+    if (row_sizes.contains(row))
+        height = row_sizes[row];
+    else
+        height = default_row_height;
+    return static_cast<int>(4.0 / 3.0 *height);
+}
+
+/*
+ Convert the width of a cell from user's units to pixels. Excel rounds
+ the column width to the nearest pixel. If the width hasn't been set
+ by the user we use the default value. If the column is hidden it
+ has a value of zero.
+*/
+int WorksheetPrivate::colPixelsSize(int col) const
+{
+    double max_digit_width = 7.0; //For Calabri 11
+    double padding = 5.0;
+    int pixels = 0;
+
+    if (col_sizes.contains(col)) {
+        double width = col_sizes[col];
+        if (width < 1)
+            pixels = static_cast<int>(width * (max_digit_width + padding) + 0.5);
+        else
+            pixels = static_cast<int>(width * max_digit_width + 0.5) + padding;
+    } else {
+        pixels = 64;
+    }
+    return pixels;
+}
+
+void WorksheetPrivate::loadXmlSheetData(QXmlStreamReader &reader)
+{
+    Q_Q(Worksheet);
+    Q_ASSERT(reader.name() == QLatin1String("sheetData"));
+
+    while (!reader.atEnd() && !(reader.name() == QLatin1String("sheetData") && reader.tokenType() == QXmlStreamReader::EndElement)) {
+        if (reader.readNextStartElement()) {
+            if (reader.name() == QLatin1String("row")) {
+                QXmlStreamAttributes attributes = reader.attributes();
+
+                if (attributes.hasAttribute(QLatin1String("customFormat"))
+                        || attributes.hasAttribute(QLatin1String("customHeight"))
+                        || attributes.hasAttribute(QLatin1String("hidden"))
+                        || attributes.hasAttribute(QLatin1String("outlineLevel"))
+                        || attributes.hasAttribute(QLatin1String("collapsed"))) {
+
+                    QSharedPointer<XlsxRowInfo> info(new XlsxRowInfo);
+                    if (attributes.hasAttribute(QLatin1String("customFormat")) && attributes.hasAttribute(QLatin1String("s"))) {
+                        int idx = attributes.value(QLatin1String("s")).toString().toInt();
+                        info->format = workbook->styles()->xfFormat(idx);
+                    }
+
+                    if (attributes.hasAttribute(QLatin1String("customHeight"))) {
+                        info->customHeight = attributes.value(QLatin1String("customHeight")) == QLatin1String("1");
+                        //Row height is only specified when customHeight is set
+                        if(attributes.hasAttribute(QLatin1String("ht"))) {
+                            info->height = attributes.value(QLatin1String("ht")).toString().toDouble();
+                        }
+                    }
+
+                    //both "hidden" and "collapsed" default are false
+                    info->hidden = attributes.value(QLatin1String("hidden")) == QLatin1String("1");
+                    info->collapsed = attributes.value(QLatin1String("collapsed")) == QLatin1String("1");
+
+                    if (attributes.hasAttribute(QLatin1String("outlineLevel")))
+                        info->outlineLevel = attributes.value(QLatin1String("outlineLevel")).toString().toInt();
+
+                    //"r" is optional too.
+                    if (attributes.hasAttribute(QLatin1String("r"))) {
+                        int row = attributes.value(QLatin1String("r")).toString().toInt();
+                        rowsInfo[row] = info;
+                    }
+                }
+
+            } else if (reader.name() == QLatin1String("c")) {  //Cell
+                QXmlStreamAttributes attributes = reader.attributes();
+                QString r = attributes.value(QLatin1String("r")).toString();
+                CellReference pos(r);
+
+                //get format
+                Format format;
+                if (attributes.hasAttribute(QLatin1String("s"))) { //"s" == style index
+                    int idx = attributes.value(QLatin1String("s")).toString().toInt();
+                    format = workbook->styles()->xfFormat(idx);
+                    ////Empty format exists in styles xf table of real .xlsx files, see issue #65.
+                    //if (!format.isValid())
+                    //    qDebug()<<QStringLiteral("<c s=\"%1\">Invalid style index: ").arg(idx)<<idx;
+                }
+
+                Cell::CellType cellType = Cell::NumberType;
+                if (attributes.hasAttribute(QLatin1String("t"))) {
+                    QString typeString = attributes.value(QLatin1String("t")).toString();
+                    if (typeString == QLatin1String("s"))
+                        cellType = Cell::SharedStringType;
+                    else if (typeString == QLatin1String("inlineStr"))
+                        cellType = Cell::InlineStringType;
+                    else if (typeString == QLatin1String("str"))
+                        cellType = Cell::StringType;
+                    else if (typeString == QLatin1String("b"))
+                        cellType = Cell::BooleanType;
+                    else if (typeString == QLatin1String("e"))
+                        cellType = Cell::ErrorType;
+                    else
+                        cellType = Cell::NumberType;
+                }
+
+                QSharedPointer<Cell> cell(new Cell(QVariant() ,cellType, format, q));
+                while (!reader.atEnd() && !(reader.name() == QLatin1String("c") && reader.tokenType() == QXmlStreamReader::EndElement)) {
+                    if (reader.readNextStartElement()) {
+                        if (reader.name() == QLatin1String("f")) {
+                            CellFormula &formula = cell->d_func()->formula;
+                            formula.loadFromXml(reader);
+                            if (formula.formulaType() == CellFormula::SharedType && !formula.formulaText().isEmpty()) {
+                                sharedFormulaMap[formula.sharedIndex()] = formula;
+                            }
+                        } else if (reader.name() == QLatin1String("v")) {
+                            QString value = reader.readElementText();
+                            if (cellType == Cell::SharedStringType) {
+                                int sst_idx = value.toInt();
+                                sharedStrings()->incRefByStringIndex(sst_idx);
+                                RichString rs = sharedStrings()->getSharedString(sst_idx);
+                                cell->d_func()->value = rs.toPlainString();
+                                if (rs.isRichString())
+                                    cell->d_func()->richString = rs;
+                            } else if (cellType == Cell::NumberType) {
+                                cell->d_func()->value = value.toDouble();
+                            } else if (cellType == Cell::BooleanType) {
+                                cell->d_func()->value = value.toInt() ? true : false;
+                            } else { //Cell::ErrorType and Cell::StringType
+                                cell->d_func()->value = value;
+                            }
+                        } else if (reader.name() == QLatin1String("is")) {
+                            while (!reader.atEnd() && !(reader.name() == QLatin1String("is") && reader.tokenType() == QXmlStreamReader::EndElement)) {
+                                if (reader.readNextStartElement()) {
+                                    //:Todo, add rich text read support
+                                    if (reader.name() == QLatin1String("t")) {
+                                        cell->d_func()->value = reader.readElementText();
+                                    }
+                                }
+                            }
+                        } else if (reader.name() == QLatin1String("extLst")) {
+                            //skip extLst element
+                            while (!reader.atEnd() && !(reader.name() == QLatin1String("extLst")
+                                                        && reader.tokenType() == QXmlStreamReader::EndElement)) {
+                                reader.readNextStartElement();
+                            }
+                        }
+                    }
+                }
+                cellTable[pos.row()][pos.column()] = cell;
+            }
+        }
+    }
+}
+
+void WorksheetPrivate::loadXmlColumnsInfo(QXmlStreamReader &reader)
+{
+    Q_ASSERT(reader.name() == QLatin1String("cols"));
+
+    while (!reader.atEnd() && !(reader.name() == QLatin1String("cols") && reader.tokenType() == QXmlStreamReader::EndElement)) {
+        reader.readNextStartElement();
+        if (reader.tokenType() == QXmlStreamReader::StartElement) {
+            if (reader.name() == QLatin1String("col")) {
+                QSharedPointer<XlsxColumnInfo> info(new XlsxColumnInfo);
+
+                QXmlStreamAttributes colAttrs = reader.attributes();
+                int min = colAttrs.value(QLatin1String("min")).toString().toInt();
+                int max = colAttrs.value(QLatin1String("max")).toString().toInt();
+                info->firstColumn = min;
+                info->lastColumn = max;
+
+                //Flag indicating that the column width for the affected column(s) is different from the
+                // default or has been manually set
+                if(colAttrs.hasAttribute(QLatin1String("customWidth"))) {
+                    info->customWidth = colAttrs.value(QLatin1String("customWidth")) == QLatin1String("1");
+                }
+                //Note, node may have "width" without "customWidth"
+                if (colAttrs.hasAttribute(QLatin1String("width"))) {
+                    double width = colAttrs.value(QLatin1String("width")).toString().toDouble();
+                    info->width = width;
+                }
+
+                info->hidden = colAttrs.value(QLatin1String("hidden")) == QLatin1String("1");
+                info->collapsed = colAttrs.value(QLatin1String("collapsed")) == QLatin1String("1");
+
+                if (colAttrs.hasAttribute(QLatin1String("style"))) {
+                    int idx = colAttrs.value(QLatin1String("style")).toString().toInt();
+                    info->format = workbook->styles()->xfFormat(idx);
+                }
+                if (colAttrs.hasAttribute(QLatin1String("outlineLevel")))
+                    info->outlineLevel = colAttrs.value(QLatin1String("outlineLevel")).toString().toInt();
+
+                colsInfo.insert(min, info);
+                for (int col=min; col<=max; ++col)
+                    colsInfoHelper[col] = info;
+            }
+        }
+    }
+}
+
+void WorksheetPrivate::loadXmlMergeCells(QXmlStreamReader &reader)
+{
+    Q_ASSERT(reader.name() == QLatin1String("mergeCells"));
+
+    QXmlStreamAttributes attributes = reader.attributes();
+    int count = attributes.value(QLatin1String("count")).toString().toInt();
+
+    while (!reader.atEnd() && !(reader.name() == QLatin1String("mergeCells") && reader.tokenType() == QXmlStreamReader::EndElement)) {
+        reader.readNextStartElement();
+        if (reader.tokenType() == QXmlStreamReader::StartElement) {
+            if (reader.name() == QLatin1String("mergeCell")) {
+                QXmlStreamAttributes attrs = reader.attributes();
+                QString rangeStr = attrs.value(QLatin1String("ref")).toString();
+                merges.append(CellRange(rangeStr));
+            }
+        }
+    }
+
+    if (merges.size() != count)
+        qDebug("read merge cells error");
+}
+
+void WorksheetPrivate::loadXmlDataValidations(QXmlStreamReader &reader)
+{
+    Q_ASSERT(reader.name() == QLatin1String("dataValidations"));
+    QXmlStreamAttributes attributes = reader.attributes();
+    int count = attributes.value(QLatin1String("count")).toString().toInt();
+
+    while (!reader.atEnd() && !(reader.name() == QLatin1String("dataValidations")
+            && reader.tokenType() == QXmlStreamReader::EndElement)) {
+        reader.readNextStartElement();
+        if (reader.tokenType() == QXmlStreamReader::StartElement
+                && reader.name() == QLatin1String("dataValidation")) {
+            dataValidationsList.append(DataValidation::loadFromXml(reader));
+        }
+    }
+
+    if (dataValidationsList.size() != count)
+        qDebug("read data validation error");
+}
+
+void WorksheetPrivate::loadXmlSheetViews(QXmlStreamReader &reader)
+{
+    Q_ASSERT(reader.name() == QLatin1String("sheetViews"));
+
+    while (!reader.atEnd() && !(reader.name() == QLatin1String("sheetViews")
+            && reader.tokenType() == QXmlStreamReader::EndElement)) {
+        reader.readNextStartElement();
+        if (reader.tokenType() == QXmlStreamReader::StartElement && reader.name() == QLatin1String("sheetView")) {
+            QXmlStreamAttributes attrs = reader.attributes();
+            //default false
+            windowProtection = attrs.value(QLatin1String("windowProtection")) == QLatin1String("1");
+            showFormulas = attrs.value(QLatin1String("showFormulas")) == QLatin1String("1");
+            rightToLeft = attrs.value(QLatin1String("rightToLeft")) == QLatin1String("1");
+            tabSelected = attrs.value(QLatin1String("tabSelected")) == QLatin1String("1");
+            //default true
+            showGridLines = attrs.value(QLatin1String("showGridLines")) != QLatin1String("0");
+            showRowColHeaders = attrs.value(QLatin1String("showRowColHeaders")) != QLatin1String("0");
+            showZeros = attrs.value(QLatin1String("showZeros")) != QLatin1String("0");
+            showRuler = attrs.value(QLatin1String("showRuler")) != QLatin1String("0");
+            showOutlineSymbols = attrs.value(QLatin1String("showOutlineSymbols")) != QLatin1String("0");
+            showWhiteSpace = attrs.value(QLatin1String("showWhiteSpace")) != QLatin1String("0");
+        }
+    }
+}
+
+void WorksheetPrivate::loadXmlSheetFormatProps(QXmlStreamReader &reader)
+{
+    Q_ASSERT(reader.name() == QLatin1String("sheetFormatPr"));
+    QXmlStreamAttributes attributes = reader.attributes();
+    XlsxSheetFormatProps formatProps;
+
+    //Retain default values
+    foreach (QXmlStreamAttribute attrib, attributes) {
+        if(attrib.name() == QLatin1String("baseColWidth") ) {
+            formatProps.baseColWidth = attrib.value().toString().toInt();
+        } else if(attrib.name() == QLatin1String("customHeight")) {
+            formatProps.customHeight = attrib.value() == QLatin1String("1");
+        } else if(attrib.name() == QLatin1String("defaultColWidth")) {
+            formatProps.defaultColWidth = attrib.value().toString().toDouble();
+        } else if(attrib.name() == QLatin1String("defaultRowHeight")) {
+            formatProps.defaultRowHeight = attrib.value().toString().toDouble();
+        } else if(attrib.name() == QLatin1String("outlineLevelCol")) {
+            formatProps.outlineLevelCol = attrib.value().toString().toInt();
+        } else if(attrib.name() == QLatin1String("outlineLevelRow")) {
+            formatProps.outlineLevelRow = attrib.value().toString().toInt();
+        } else if(attrib.name() == QLatin1String("thickBottom")) {
+            formatProps.thickBottom = attrib.value() == QLatin1String("1");
+        } else if(attrib.name() == QLatin1String("thickTop")) {
+            formatProps.thickTop  = attrib.value() == QLatin1String("1");
+        } else if(attrib.name() == QLatin1String("zeroHeight")) {
+            formatProps.zeroHeight = attrib.value() == QLatin1String("1");
+        }
+    }
+
+    if(formatProps.defaultColWidth == 0.0) { //not set
+       formatProps.defaultColWidth = WorksheetPrivate::calculateColWidth(formatProps.baseColWidth);
+    }
+
+}
+double WorksheetPrivate::calculateColWidth(int characters)
+{
+    //!Todo
+    //Take normal style' font maximum width and add padding and margin pixels
+    return characters + 0.5;
+}
+
+void WorksheetPrivate::loadXmlHyperlinks(QXmlStreamReader &reader)
+{
+    Q_ASSERT(reader.name() == QLatin1String("hyperlinks"));
+
+    while (!reader.atEnd() && !(reader.name() == QLatin1String("hyperlinks")
+            && reader.tokenType() == QXmlStreamReader::EndElement)) {
+        reader.readNextStartElement();
+        if (reader.tokenType() == QXmlStreamReader::StartElement && reader.name() == QLatin1String("hyperlink")) {
+            QXmlStreamAttributes attrs = reader.attributes();
+            CellReference pos(attrs.value(QLatin1String("ref")).toString());
+            if (pos.isValid()) { //Valid
+                QSharedPointer<XlsxHyperlinkData> link(new XlsxHyperlinkData);
+                link->display = attrs.value(QLatin1String("display")).toString();
+                link->tooltip = attrs.value(QLatin1String("tooltip")).toString();
+                link->location = attrs.value(QLatin1String("location")).toString();
+
+                if (attrs.hasAttribute(QLatin1String("r:id"))) {
+                    link->linkType = XlsxHyperlinkData::External;
+                    XlsxRelationship ship = relationships->getRelationshipById(attrs.value(QLatin1String("r:id")).toString());
+                    link->target = ship.target;
+                } else {
+                    link->linkType = XlsxHyperlinkData::Internal;
+                }
+
+                urlTable[pos.row()][pos.column()] = link;
+            }
+        }
+    }
+}
+
+QList <QSharedPointer<XlsxColumnInfo> > WorksheetPrivate::getColumnInfoList(int colFirst, int colLast)
+{
+    QList <QSharedPointer<XlsxColumnInfo> > columnsInfoList;
+    if(isColumnRangeValid(colFirst,colLast))
+    {
+        QList<int> nodes = getColumnIndexes(colFirst, colLast);
+
+        for (int idx = 0; idx < nodes.size(); ++idx) {
+            int colStart = nodes[idx];
+            if (colsInfo.contains(colStart)) {
+                QSharedPointer<XlsxColumnInfo> info = colsInfo[colStart];
+                columnsInfoList.append(info);
+            } else {
+                int colEnd = (idx == nodes.size() - 1) ? colLast : nodes[idx+1] - 1;
+                QSharedPointer<XlsxColumnInfo> info(new XlsxColumnInfo(colStart, colEnd));
+                colsInfo.insert(colFirst, info);
+                columnsInfoList.append(info);
+                for (int c = colStart; c <= colEnd; ++c)
+                    colsInfoHelper[c] = info;
+            }
+        }
+    }
+
+    return columnsInfoList;
+}
+
+QList <QSharedPointer<XlsxRowInfo> > WorksheetPrivate::getRowInfoList(int rowFirst, int rowLast)
+{
+    QList <QSharedPointer<XlsxRowInfo> > rowInfoList;
+
+    int min_col = dimension.firstColumn() < 1 ? 1 : dimension.firstColumn();
+
+    for(int row = rowFirst; row <= rowLast; ++row) {
+        if (checkDimensions(row, min_col, false, true))
+            continue;
+
+        QSharedPointer<XlsxRowInfo> rowInfo;
+        if ((rowsInfo[row]).isNull()){
+            rowsInfo[row] = QSharedPointer<XlsxRowInfo>(new XlsxRowInfo());
+        }
+        rowInfoList.append(rowsInfo[row]);
+    }
+
+    return rowInfoList;
+}
+
+bool Worksheet::loadFromXmlFile(QIODevice *device)
+{
+    Q_D(Worksheet);
+
+    QXmlStreamReader reader(device);
+    while (!reader.atEnd()) {
+        reader.readNextStartElement();
+        if (reader.tokenType() == QXmlStreamReader::StartElement) {
+            if (reader.name() == QLatin1String("dimension")) {
+                QXmlStreamAttributes attributes = reader.attributes();
+                QString range = attributes.value(QLatin1String("ref")).toString();
+                d->dimension = CellRange(range);
+            } else if (reader.name() == QLatin1String("sheetViews")) {
+                d->loadXmlSheetViews(reader);
+            } else if (reader.name() == QLatin1String("sheetFormatPr")) {
+                d->loadXmlSheetFormatProps(reader);
+            } else if (reader.name() == QLatin1String("cols")) {
+                d->loadXmlColumnsInfo(reader);
+            } else if (reader.name() == QLatin1String("sheetData")) {
+                d->loadXmlSheetData(reader);
+            } else if (reader.name() == QLatin1String("mergeCells")) {
+                d->loadXmlMergeCells(reader);
+            } else if (reader.name() == QLatin1String("dataValidations")) {
+                d->loadXmlDataValidations(reader);
+            } else if (reader.name() == QLatin1String("conditionalFormatting")) {
+                ConditionalFormatting cf;
+                cf.loadFromXml(reader, workbook()->styles());
+                d->conditionalFormattingList.append(cf);
+            } else if (reader.name() == QLatin1String("hyperlinks")) {
+                d->loadXmlHyperlinks(reader);
+            } else if (reader.name() == QLatin1String("drawing")) {
+                QString rId = reader.attributes().value(QStringLiteral("r:id")).toString();
+                QString name = d->relationships->getRelationshipById(rId).target;
+                QString path = QDir::cleanPath(splitPath(filePath())[0] + QLatin1String("/") + name);
+                d->drawing = QSharedPointer<Drawing>(new Drawing(this, F_LoadFromExists));
+                d->drawing->setFilePath(path);
+            } else if (reader.name() == QLatin1String("extLst")) {
+                //Todo: add extLst support
+                while (!reader.atEnd() && !(reader.name() == QLatin1String("extLst")
+                                            && reader.tokenType() == QXmlStreamReader::EndElement)) {
+                    reader.readNextStartElement();
+                }
+            }
+        }
+    }
+
+    d->validateDimension();
+    return true;
+}
+
+/*
+ *  Documents imported from Google Docs does not contain dimension data.
+ */
+void WorksheetPrivate::validateDimension()
+{
+    if (dimension.isValid() || cellTable.isEmpty())
+        return;
+
+    int firstRow = cellTable.constBegin().key();
+    int lastRow = (cellTable.constEnd()-1).key();
+    int firstColumn = -1;
+    int lastColumn = -1;
+
+    for (QMap<int, QMap<int, QSharedPointer<Cell> > >::const_iterator it = cellTable.begin(); it != cellTable.end(); ++it)
+    {
+        Q_ASSERT(!it.value().isEmpty());
+
+        if (firstColumn == -1 || it.value().constBegin().key() < firstColumn)
+            firstColumn = it.value().constBegin().key();
+
+        if (lastColumn == -1 || (it.value().constEnd()-1).key() > lastColumn)
+            lastColumn = (it.value().constEnd()-1).key();
+    }
+
+    CellRange cr(firstRow, firstColumn, lastRow, lastColumn);
+
+    if (cr.isValid())
+        dimension = cr;
+}
+
+/*!
+ * \internal
+ *  Unit test can use this member to get sharedString object.
+ */
+SharedStrings *WorksheetPrivate::sharedStrings() const
+{
+    return workbook->sharedStrings();
+}
+
+QT_END_NAMESPACE_XLSX
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)