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