Mercurial > clickerconvert
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