[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-commits
Subject: [calligra] sheets: more work on odf code separation
From: Tomas Mecir <mecirt () gmail ! com>
Date: 2016-03-06 17:21:11
Message-ID: E1accMh-0005vS-9K () scm ! kde ! org
[Download RAW message or body]
Git commit 0356edee381f36b301d0774f40cbc3cccfb7c03f by Tomas Mecir.
Committed on 24/01/2016 at 23:33.
Pushed by mecir into branch 'master'.
more work on odf code separation
M +1 -0 sheets/CMakeLists.txt
M +0 -887 sheets/Cell.cpp
M +0 -54 sheets/Cell.h
M +89 -14 sheets/Style.cpp
M +0 -74 sheets/Util.cpp
M +0 -14 sheets/Util.h
M +934 -0 sheets/odf/SheetsOdfCell.cpp
M +18 -7 sheets/odf/SheetsOdfSheet.cpp
http://commits.kde.org/calligra/0356edee381f36b301d0774f40cbc3cccfb7c03f
diff --git a/sheets/CMakeLists.txt b/sheets/CMakeLists.txt
index b6c6a25..7b75778 100644
--- a/sheets/CMakeLists.txt
+++ b/sheets/CMakeLists.txt
@@ -150,6 +150,7 @@ set (odf_DIR_SRCS
odf/SheetsOdfMap.cpp
odf/SheetsOdfSheet.cpp
odf/SheetsOdfCell.cpp
+ odf/SheetsOdfStyle.cpp
)
set (part_DIR_SRCS
diff --git a/sheets/Cell.cpp b/sheets/Cell.cpp
index 3ded273..92f8377 100644
--- a/sheets/Cell.cpp
+++ b/sheets/Cell.cpp
@@ -52,17 +52,13 @@
#include "CellStorage.h"
#include "Condition.h"
#include "Formula.h"
-#include "GenValidationStyle.h"
#include "Global.h"
#include "Localization.h"
#include "LoadingInfo.h"
#include "Map.h"
#include "NamedAreaManager.h"
-#include "OdfLoadingContext.h"
-#include "OdfSavingContext.h"
#include "RowColumnFormat.h"
#include "RowFormatStorage.h"
-#include "ShapeApplicationData.h"
#include "Sheet.h"
#include "Style.h"
#include "StyleManager.h"
@@ -74,19 +70,8 @@
#include "ValueParser.h"
#include "StyleStorage.h"
-#include <KoUnit.h>
-#include <KoShape.h>
-#include <KoShapeLoadingContext.h>
-#include <KoShapeRegistry.h>
-#include <KoXmlNS.h>
#include <KoXmlReader.h>
-#include <KoXmlWriter.h>
-#include <KoTextLoader.h>
-#include <KoStyleManager.h>
-#include <KoTextSharedLoadingData.h>
-#include <KoTextDocument.h>
-#include <KoTextWriter.h>
#include <KoParagraphStyle.h>
#include <QTimer>
@@ -1069,878 +1054,6 @@ bool Cell::saveCellResult(QDomDocument& doc, QDomElement& \
result,
return true; /* really isn't much of a way for this function to fail */
}
-void Cell::saveOdfAnnotation(KoXmlWriter &xmlwriter)
-{
- const QString comment = this->comment();
- if (!comment.isEmpty()) {
- //<office:annotation draw:style-name="gr1" draw:text-style-name="P1" \
svg:width="2.899cm" svg:height="2.691cm" svg:x="2.858cm" svg:y="0.001cm" \
draw:caption-point-x="-2.858cm" draw:caption-point-y="-0.001cm">
- xmlwriter.startElement("office:annotation");
- const QStringList text = comment.split('\n', QString::SkipEmptyParts);
- for (QStringList::ConstIterator it = text.begin(); it != text.end(); ++it) {
- xmlwriter.startElement("text:p");
- xmlwriter.addTextNode(*it);
- xmlwriter.endElement();
- }
- xmlwriter.endElement();
- }
-}
-
-QString Cell::saveOdfCellStyle(KoGenStyle ¤tCellStyle, KoGenStyles \
&mainStyles)
-{
- const Conditions conditions = this->conditions();
- if (!conditions.isEmpty()) {
- // this has to be an automatic style
- currentCellStyle = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell");
- conditions.saveOdfConditions(currentCellStyle, sheet()->map()->converter());
- }
- return style().saveOdf(currentCellStyle, mainStyles, \
d->sheet->map()->styleManager());
-}
-
-
-bool Cell::saveOdf(int row, int column, int &repeated,
- OdfSavingContext& tableContext)
-{
- KoXmlWriter & xmlwriter = tableContext.shapeContext.xmlWriter();
- KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
-
- // see: OpenDocument, 8.1.3 Table Cell
- if (!isPartOfMerged())
- xmlwriter.startElement("table:table-cell");
- else
- xmlwriter.startElement("table:covered-table-cell");
-#if 0
- //add font style
- QFont font;
- Value const value(cell.value());
- if (!cell.isDefault()) {
- font = cell.format()->textFont(i, row);
- m_styles.addFont(font);
-
- if (cell.format()->hasProperty(Style::SComment))
- hasComment = true;
- }
-#endif
- // NOTE save the value before the style as long as the Formatter does not work \
correctly
- if (link().isEmpty())
- saveOdfValue(xmlwriter);
-
- const Style cellStyle = style();
-
- // Either there's no column and row default and the style's not the default \
style,
- // or the style is different to one of them. The row default takes precedence.
- if ((!tableContext.rowDefaultStyles.contains(row) &&
- !tableContext.columnDefaultStyles.contains(column) &&
- !(cellStyle.isDefault() && conditions().isEmpty())) ||
- (tableContext.rowDefaultStyles.contains(row) && \
tableContext.rowDefaultStyles[row] != cellStyle) ||
- (tableContext.columnDefaultStyles.contains(column) && \
tableContext.columnDefaultStyles[column] != cellStyle)) {
- KoGenStyle currentCellStyle; // the type determined in saveOdfCellStyle
- QString styleName = saveOdfCellStyle(currentCellStyle, mainStyles);
- // skip 'table:style-name' attribute for the default style
- if (!currentCellStyle.isDefaultStyle()) {
- if (!styleName.isEmpty())
- xmlwriter.addAttribute("table:style-name", styleName);
- }
- }
-
- // group empty cells with the same style
- const QString comment = this->comment();
- if (isEmpty() && comment.isEmpty() && !isPartOfMerged() && !doesMergeCells() &&
- !tableContext.cellHasAnchoredShapes(sheet(), row, column)) {
- bool refCellIsDefault = isDefault();
- int j = column + 1;
- Cell nextCell = sheet()->cellStorage()->nextInRow(column, row);
- while (!nextCell.isNull()) {
- // if
- // the next cell is not the adjacent one
- // or
- // the next cell is not empty
- if (nextCell.column() != j || (!nextCell.isEmpty() || \
tableContext.cellHasAnchoredShapes(sheet(), row, column))) {
- if (refCellIsDefault) {
- // if the origin cell was a default cell,
- // we count the default cells
- repeated = nextCell.column() - j + 1;
-
- // check if any of the empty/default cells we skipped contained \
anchored shapes
- int shapeColumn = tableContext.nextAnchoredShape(sheet(), row, \
column);
- if (shapeColumn) {
- repeated = qMin(repeated, shapeColumn - column);
- }
- }
- // otherwise we just stop here to process the adjacent
- // cell in the next iteration of the outer loop
- // (in Sheet::saveOdfCells)
- break;
- }
-
- if (nextCell.isPartOfMerged() || nextCell.doesMergeCells() ||
- !nextCell.comment().isEmpty() || \
tableContext.cellHasAnchoredShapes(sheet(), row, nextCell.column()) \
||
- !(nextCell.style() == cellStyle && nextCell.conditions() == \
conditions())) {
- break;
- }
- ++repeated;
- // get the next cell and set the index to the adjacent cell
- nextCell = sheet()->cellStorage()->nextInRow(j++, row);
- }
- //debugSheetsODF << "Cell::saveOdf: empty cell in column" << column
- //<< "repeated" << repeated << "time(s)" << endl;
-
- if (repeated > 1)
- xmlwriter.addAttribute("table:number-columns-repeated", \
QString::number(repeated));
- }
-
- Validity validity = Cell(sheet(), column, row).validity();
- if (!validity.isEmpty()) {
- GenValidationStyle styleVal(&validity, sheet()->map()->converter());
- xmlwriter.addAttribute("table:validation-name", \
tableContext.valStyle.insert(styleVal));
- }
- if (isFormula()) {
- //debugSheetsODF <<"Formula found";
- QString formula = Odf::encodeFormula(userInput(), locale());
- xmlwriter.addAttribute("table:formula", formula);
- }
-
- if (doesMergeCells()) {
- int colSpan = mergedXCells() + 1;
- int rowSpan = mergedYCells() + 1;
-
- if (colSpan > 1)
- xmlwriter.addAttribute("table:number-columns-spanned", \
QString::number(colSpan));
-
- if (rowSpan > 1)
- xmlwriter.addAttribute("table:number-rows-spanned", \
QString::number(rowSpan));
- }
-
- saveOdfAnnotation(xmlwriter);
-
- if (!isFormula() && !link().isEmpty()) {
- //debugSheetsODF<<"Link found";
- xmlwriter.startElement("text:p");
- xmlwriter.startElement("text:a");
- const QString url = link();
- //Reference cell is started by '#'
- if (Util::localReferenceAnchor(url))
- xmlwriter.addAttribute("xlink:href", ('#' + url));
- else
- xmlwriter.addAttribute("xlink:href", url);
- xmlwriter.addAttribute("xlink:type", "simple");
- xmlwriter.addTextNode(userInput());
- xmlwriter.endElement();
- xmlwriter.endElement();
- }
-
- if (!isEmpty() && link().isEmpty()) {
- QSharedPointer<QTextDocument> doc = richText();
- if (doc) {
- QTextCharFormat format = style().asCharFormat();
- ((KoCharacterStyle \
*)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
-
- KoTextWriter writer(tableContext.shapeContext);
-
- writer.write(doc.data(), 0);
- } else {
- xmlwriter.startElement("text:p");
- xmlwriter.addTextNode(displayText().toUtf8());
- xmlwriter.endElement();
- }
- }
-
- // flake
- // Save shapes that are anchored to this cell.
- // see: OpenDocument, 2.3.1 Text Documents
- // see: OpenDocument, 9.2 Drawing Shapes
- if (tableContext.cellHasAnchoredShapes(sheet(), row, column)) {
- const QList<KoShape*> shapes = tableContext.cellAnchoredShapes(sheet(), row, \
column);
- for (int i = 0; i < shapes.count(); ++i) {
- KoShape* const shape = shapes[i];
- const QPointF bottomRight = shape->boundingRect().bottomRight();
- qreal endX = 0.0;
- qreal endY = 0.0;
- const int scol = sheet()->leftColumn(bottomRight.x(), endX);
- const int srow = sheet()->topRow(bottomRight.y(), endY);
- qreal offsetX = sheet()->columnPosition(column);
- qreal offsetY = sheet()->rowPosition(row);
- tableContext.shapeContext.addShapeOffset(shape, \
QTransform::fromTranslate(-offsetX, -offsetY));
- shape->setAdditionalAttribute("table:end-cell-address", \
Region(QPoint(scol, srow)).saveOdf());
- shape->setAdditionalAttribute("table:end-x", \
QString::number(bottomRight.x() - endX) + "pt");
- shape->setAdditionalAttribute("table:end-y", \
QString::number(bottomRight.y() - endY) + "pt");
- shape->saveOdf(tableContext.shapeContext);
- shape->removeAdditionalAttribute("table:end-cell-address");
- shape->removeAdditionalAttribute("table:end-x");
- shape->removeAdditionalAttribute("table:end-y");
- tableContext.shapeContext.removeShapeOffset(shape);
- }
- }
-
- xmlwriter.endElement();
- return true;
-}
-
-void Cell::saveOdfValue(KoXmlWriter &xmlWriter)
-{
- // Determine the format that we will be storing.
- // This is usually the format that is actually shown - doing so mixes style and \
content, but that's how
- // LO does it, so we need to stay compatible
- Format::Type shownFormat = style().formatType();
- if (shownFormat == Format::Generic)
- shownFormat = sheet()->map()->formatter()->determineFormatting(value(), \
shownFormat);
- Value::Format saveFormat = Value::fmt_None;
- Value::Format valueFormat = value().format();
- if (valueFormat == Value::fmt_Boolean)
- saveFormat = Value::fmt_Boolean;
- else if (valueFormat == Value::fmt_String) // if it's a text, it needs to be \
stored as a text
- saveFormat = Value::fmt_String;
- else if (Format::isDate(shownFormat))
- saveFormat = Value::fmt_Date;
- else if (Format::isTime(shownFormat))
- saveFormat = Value::fmt_Time;
- else if (Format::isNumber(shownFormat))
- saveFormat = Value::fmt_Number;
- else if (Format::isMoney(shownFormat))
- saveFormat = Value::fmt_Money;
- else if (shownFormat == Format::Percentage)
- saveFormat = Value::fmt_Percent;
- else if (shownFormat == Format::Text)
- saveFormat = Value::fmt_String;
- else if (shownFormat == Format::Custom)
- saveFormat = valueFormat;
-
- switch (saveFormat) {
- case Value::fmt_None: break; //NOTHING HERE
- case Value::fmt_Boolean: {
- xmlWriter.addAttribute("office:value-type", "boolean");
- xmlWriter.addAttribute("office:boolean-value", (value().asBoolean() ? "true" \
: "false"));
- break;
- }
- case Value::fmt_Number: {
- xmlWriter.addAttribute("office:value-type", "float");
- if (value().isInteger())
- xmlWriter.addAttribute("office:value", \
QString::number(value().asInteger()));
- else
- xmlWriter.addAttribute("office:value", \
QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG));
- break;
- }
- case Value::fmt_Percent: {
- xmlWriter.addAttribute("office:value-type", "percentage");
- xmlWriter.addAttribute("office:value", QString::number((double) \
numToDouble(value().asFloat())));
- break;
- }
- case Value::fmt_Money: {
- xmlWriter.addAttribute("office:value-type", "currency");
- const Style style = this->style();
- if (style.hasAttribute(Style::CurrencyFormat)) {
- Currency currency = style.currency();
- xmlWriter.addAttribute("office:currency", currency.code());
- }
- xmlWriter.addAttribute("office:value", QString::number((double) \
numToDouble(value().asFloat())));
- break;
- }
- case Value::fmt_DateTime: break; //NOTHING HERE
- case Value::fmt_Date: {
- xmlWriter.addAttribute("office:value-type", "date");
- xmlWriter.addAttribute("office:date-value",
- \
value().asDate(sheet()->map()->calculationSettings()).toString(Qt::ISODate));
- break;
- }
- case Value::fmt_Time: {
- xmlWriter.addAttribute("office:value-type", "time");
- xmlWriter.addAttribute("office:time-value",
- value().asTime().toString("'PT'hh'H'mm'M'ss'S'"));
- break;
- }
- case Value::fmt_String: {
- xmlWriter.addAttribute("office:value-type", "string");
- xmlWriter.addAttribute("office:string-value", value().asString());
- break;
- }
- };
-}
-
-bool Cell::loadOdf(const KoXmlElement& element, OdfLoadingContext& tableContext,
- const Styles& autoStyles, const QString& cellStyleName,
- QList<ShapeLoadingData>& shapeData)
-{
- static const QString sFormula = QString::fromLatin1("formula");
- static const QString sValidationName = \
QString::fromLatin1("validation-name");
- static const QString sValueType = QString::fromLatin1("value-type");
- static const QString sBoolean = QString::fromLatin1("boolean");
- static const QString sBooleanValue = QString::fromLatin1("boolean-value");
- static const QString sTrue = QString::fromLatin1("true");
- static const QString sFalse = QString::fromLatin1("false");
- static const QString sFloat = QString::fromLatin1("float");
- static const QString sValue = QString::fromLatin1("value");
- static const QString sCurrency = QString::fromLatin1("currency");
- static const QString sPercentage = QString::fromLatin1("percentage");
- static const QString sDate = QString::fromLatin1("date");
- static const QString sDateValue = QString::fromLatin1("date-value");
- static const QString sTime = QString::fromLatin1("time");
- static const QString sTimeValue = QString::fromLatin1("time-value");
- static const QString sString = QString::fromLatin1("string");
- static const QString sStringValue = QString::fromLatin1("string-value");
- static const QString sNumberColumnsSpanned = \
QString::fromLatin1("number-columns-spanned");
- static const QString sNumberRowsSpanned = \
QString::fromLatin1("number-rows-spanned");
- static const QString sAnnotation = QString::fromLatin1("annotation");
- static const QString sP = QString::fromLatin1("p");
-
- static const QStringList formulaNSPrefixes = QStringList() << "oooc:" << "kspr:" \
<< "of:" << "msoxl:";
-
- //Search and load each paragraph of text. Each paragraph is separated by a line \
break.
- loadOdfCellText(element, tableContext, autoStyles, cellStyleName);
-
- //
- // formula
- //
- bool isFormula = false;
- if (element.hasAttributeNS(KoXmlNS::table, sFormula)) {
- isFormula = true;
- QString oasisFormula(element.attributeNS(KoXmlNS::table, sFormula, \
QString()));
- // debugSheetsODF << "cell:" << name() << "formula :" << oasisFormula;
- // each spreadsheet application likes to safe formulas with a different \
namespace
- // prefix, so remove all of them
- QString namespacePrefix;
- foreach(const QString &prefix, formulaNSPrefixes) {
- if (oasisFormula.startsWith(prefix)) {
- oasisFormula.remove(0, prefix.length());
- namespacePrefix = prefix;
- break;
- }
- }
- oasisFormula = Odf::decodeFormula(oasisFormula, locale(), namespacePrefix);
- setUserInput(oasisFormula);
- } else if (!userInput().isEmpty() && userInput().at(0) == '=') //prepend ' to \
the text to avoid = to be painted
- setUserInput(userInput().prepend('\''));
-
- //
- // validation
- //
- if (element.hasAttributeNS(KoXmlNS::table, sValidationName)) {
- const QString validationName = element.attributeNS(KoXmlNS::table, \
sValidationName, QString());
- debugSheetsODF << "cell:" << name() << sValidationName << validationName;
- Validity validity;
- validity.loadOdfValidation(this, validationName, tableContext);
- if (!validity.isEmpty())
- setValidity(validity);
- }
-
- //
- // value type
- //
- if (element.hasAttributeNS(KoXmlNS::office, sValueType)) {
- const QString valuetype = element.attributeNS(KoXmlNS::office, sValueType, \
QString());
- // debugSheetsODF << "cell:" << name() << "value-type:" << valuetype;
- if (valuetype == sBoolean) {
- const QString val = element.attributeNS(KoXmlNS::office, sBooleanValue, \
QString()).toLower();
- if ((val == sTrue) || (val == sFalse))
- setValue(Value(val == sTrue));
- }
-
- // integer and floating-point value
- else if (valuetype == sFloat) {
- bool ok = false;
- Value value(element.attributeNS(KoXmlNS::office, sValue, \
QString()).toDouble(&ok));
- if (ok) {
- value.setFormat(Value::fmt_Number);
- setValue(value);
-#if 0
- Style style;
- style.setFormatType(Format::Number);
- setStyle(style);
-#endif
- }
- // always set the userInput to the actual value read from the cell, and \
not whatever happens to be set as text, as the textual representation of a value may \
be less accurate than the value itself
- if (!isFormula)
- setUserInput(sheet()->map()->converter()->asString(value).asString());
- }
-
- // currency value
- else if (valuetype == sCurrency) {
- bool ok = false;
- Value value(element.attributeNS(KoXmlNS::office, sValue, \
QString()).toDouble(&ok));
- if (ok) {
- value.setFormat(Value::fmt_Money);
- setValue(value);
-
- Currency currency;
- if (element.hasAttributeNS(KoXmlNS::office, sCurrency)) {
- currency = Currency(element.attributeNS(KoXmlNS::office, \
sCurrency, QString()));
- }
- /* TODO: somehow make this work again, all setStyle calls here will \
be overwritten by cell styles later
- if( style.isEmpty() ) {
- Style style;
- style.setCurrency(currency);
- setStyle(style);
- } */
- }
- } else if (valuetype == sPercentage) {
- bool ok = false;
- Value value(element.attributeNS(KoXmlNS::office, sValue, \
QString()).toDouble(&ok));
- if (ok) {
- value.setFormat(Value::fmt_Percent);
- setValue(value);
- if (!isFormula && userInput().isEmpty())
- \
setUserInput(sheet()->map()->converter()->asString(value).asString());
-// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
-#if 0
- Style style;
- style.setFormatType(Format::Percentage);
- setStyle(style);
-#endif
- }
- } else if (valuetype == sDate) {
- QString value = element.attributeNS(KoXmlNS::office, sDateValue, \
QString());
-
- // "1980-10-15" or "2001-01-01T19:27:41"
- int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0;
- bool hasTime = false;
- bool ok = false;
-
- int p1 = value.indexOf('-');
- if (p1 > 0) {
- year = value.left(p1).toInt(&ok);
- if (ok) {
- int p2 = value.indexOf('-', ++p1);
- month = value.mid(p1, p2 - p1).toInt(&ok);
- if (ok) {
- // the date can optionally have a time attached
- int p3 = value.indexOf('T', ++p2);
- if (p3 > 0) {
- hasTime = true;
- day = value.mid(p2, p3 - p2).toInt(&ok);
- if (ok) {
- int p4 = value.indexOf(':', ++p3);
- hours = value.mid(p3, p4 - p3).toInt(&ok);
- if (ok) {
- int p5 = value.indexOf(':', ++p4);
- minutes = value.mid(p4, p5 - p4).toInt(&ok);
- if (ok)
- seconds = value.right(value.length() - p5 - \
1).toInt(&ok);
- }
- }
- } else {
- day = value.right(value.length() - p2).toInt(&ok);
- }
- }
- }
- }
-
- if (ok) {
- if (hasTime)
- setValue(Value(QDateTime(QDate(year, month, day), QTime(hours, \
minutes, seconds)), sheet()->map()->calculationSettings()));
- else
- setValue(Value(QDate(year, month, day), \
sheet()->map()->calculationSettings()));
-// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
-//Sebsauer: Fixed now. Value::Format handles it correct.
-#if 0
- Style s;
- s.setFormatType(Format::ShortDate);
- setStyle(s);
-#endif
- // debugSheetsODF << "cell:" << name() << "Type: date, value:" << \
value << "Date:" << year << " -" << month << " -" << day;
- }
- } else if (valuetype == sTime) {
- QString value = element.attributeNS(KoXmlNS::office, sTimeValue, \
QString());
-
- // "PT15H10M12S"
- int hours = 0, minutes = 0, seconds = 0;
- int l = value.length();
- QString num;
- bool ok = false;
- for (int i = 0; i < l; ++i) {
- if (value[i].isNumber()) {
- num += value[i];
- continue;
- } else if (value[i] == 'H')
- hours = num.toInt(&ok);
- else if (value[i] == 'M')
- minutes = num.toInt(&ok);
- else if (value[i] == 'S')
- seconds = num.toInt(&ok);
- else
- continue;
- //debugSheetsODF << "Num:" << num;
- num.clear();
- if (!ok)
- break;
- }
-
- if (ok) {
- // Value kval( timeToNum( hours, minutes, seconds ) );
- // cell.setValue( kval );
- setValue(Value(QTime(hours % 24, minutes, seconds)));
-// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
-#if 0
- Style style;
- style.setFormatType(Format::Time);
- setStyle(style);
-#endif
- // debugSheetsODF << "cell:" << name() << "Type: time:" << value << \
"Hours:" << hours << "," << minutes << "," << seconds;
- }
- } else if (valuetype == sString) {
- if (element.hasAttributeNS(KoXmlNS::office, sStringValue)) {
- QString value = element.attributeNS(KoXmlNS::office, sStringValue, \
QString());
- setValue(Value(value));
- } else {
- // use the paragraph(s) read in before
- setValue(Value(userInput()));
- }
-// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
-#if 0
- Style style;
- style.setFormatType(Format::Text);
- setStyle(style);
-#endif
- } else {
- // debugSheetsODF << "cell:" << name() << " Unknown type. Parsing user \
input.";
- // Set the value by parsing the user input.
- parseUserInput(userInput());
- }
- } else { // no value-type attribute
- // debugSheetsODF << "cell:" << name() << " No value type specified. \
Parsing user input.";
- // Set the value by parsing the user input.
- parseUserInput(userInput());
- }
-
- //
- // merged cells ?
- //
- int colSpan = 1;
- int rowSpan = 1;
- if (element.hasAttributeNS(KoXmlNS::table, sNumberColumnsSpanned)) {
- bool ok = false;
- int span = element.attributeNS(KoXmlNS::table, sNumberColumnsSpanned, \
QString()).toInt(&ok);
- if (ok) colSpan = span;
- }
- if (element.hasAttributeNS(KoXmlNS::table, sNumberRowsSpanned)) {
- bool ok = false;
- int span = element.attributeNS(KoXmlNS::table, sNumberRowsSpanned, \
QString()).toInt(&ok);
- if (ok) rowSpan = span;
- }
- if (colSpan > 1 || rowSpan > 1)
- mergeCells(d->column, d->row, colSpan - 1, rowSpan - 1);
-
- //
- // cell comment/annotation
- //
- KoXmlElement annotationElement = KoXml::namedItemNS(element, KoXmlNS::office, \
sAnnotation);
- if (!annotationElement.isNull()) {
- QString comment;
- KoXmlNode node = annotationElement.firstChild();
- while (!node.isNull()) {
- KoXmlElement commentElement = node.toElement();
- if (!commentElement.isNull())
- if (commentElement.localName() == sP && \
commentElement.namespaceURI() == KoXmlNS::text) {
- if (!comment.isEmpty()) comment.append('\n');
- comment.append(commentElement.text());
- }
-
- node = node.nextSibling();
- }
- if (!comment.isEmpty())
- setComment(comment);
- }
-
- loadOdfObjects(element, tableContext, shapeData);
-
- return true;
-}
-
-// Similar to KoXml::namedItemNS except that children of span tags will be evaluated \
too.
-KoXmlElement namedItemNSWithSpan(const KoXmlNode& node, const QString &nsURI, const \
QString &localName)
-{
- KoXmlNode n = node.firstChild();
- for (; !n.isNull(); n = n.nextSibling()) {
- if (n.isElement()) {
- if (n.localName() == localName && n.namespaceURI() == nsURI) {
- return n.toElement();
- }
- if (n.localName() == "span" && n.namespaceURI() == nsURI) {
- KoXmlElement e = KoXml::namedItemNS(n, nsURI, localName); // not \
recursive
- if (!e.isNull()) {
- return e;
- }
- }
- }
- }
- return KoXmlElement();
-}
-
-// recursively goes through all children of parent and returns true if there is any \
element
-// in the draw: namespace in this subtree
-static bool findDrawElements(const KoXmlElement& parent)
-{
- KoXmlElement element;
- forEachElement(element , parent) {
- if (element.namespaceURI() == KoXmlNS::draw)
- return true;
- if (findDrawElements(element))
- return true;
- }
- return false;
-}
-
-QString loadOdfCellTextNodes(const KoXmlElement& element, int *textFragmentCount, \
int *lineCount, bool *hasRichText, bool *stripLeadingSpace)
-{
- QString cellText;
- bool countedOwnFragments = false;
- bool prevWasText = false;
- for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
- if (n.isText()) {
- prevWasText = true;
- QString t = KoTextLoader::normalizeWhitespace(n.toText().data(), \
*stripLeadingSpace);
- if (!t.isEmpty()) {
- *stripLeadingSpace = t[t.length() - 1].isSpace();
- cellText += t;
- if (!countedOwnFragments) {
- // We only count the number of different parent elements which \
have text. That is
- // so cause different parent-elements may mean different styles \
which means
- // rich-text while the same parent element means the same style \
so we can easily
- // put them together into one string.
- countedOwnFragments = true;
- ++(*textFragmentCount);
- }
- }
- } else {
- KoXmlElement e = n.toElement();
- if (!e.isNull()) {
- if (prevWasText && !cellText.isEmpty() && cellText[cellText.length() \
- 1].isSpace()) {
- // A trailing space of the cellText collected so far needs to be \
preserved when
- // more text-nodes within the same parent follow but if an \
element like e.g.
- // text:s follows then a trailing space needs to be removed.
- cellText.chop(1);
- }
- prevWasText = false;
-
- // We can optimize some elements like text:s (space), text:tab \
(tabulator) and
- // text:line-break (new-line) to not produce rich-text but add the \
equivalent
- // for them in plain-text.
- const bool isTextNs = e.namespaceURI() == KoXmlNS::text;
- if (isTextNs && e.localName() == "s") {
- const int howmany = qMax(1, e.attributeNS(KoXmlNS::text, "c", \
QString()).toInt());
- cellText += QString().fill(32, howmany);
- } else if (isTextNs && e.localName() == "tab") {
- cellText += '\t';
- } else if (isTextNs && e.localName() == "line-break") {
- cellText += '\n';
- ++(*lineCount);
- } else if (isTextNs && e.localName() == "span") {
- // Nested span-elements means recursive evaluation.
- cellText += loadOdfCellTextNodes(e, textFragmentCount, \
lineCount, hasRichText, stripLeadingSpace);
- } else if (!isTextNs ||
- ( e.localName() != "annotation" &&
- e.localName() != "bookmark" &&
- e.localName() != "meta" &&
- e.localName() != "tag" )) {
- // Seems we have an element we cannot easily translate to a \
string what
- // means it's all rich-text now.
- *hasRichText = true;
- }
- }
- }
- }
- return cellText;
-}
-
-void Cell::loadOdfCellText(const KoXmlElement& parent, OdfLoadingContext& \
tableContext, const Styles& autoStyles, const QString& cellStyleName)
-{
- //Search and load each paragraph of text. Each paragraph is separated by a line \
break
- KoXmlElement textParagraphElement;
- QString cellText;
-
- int lineCount = 0;
- bool hasRichText = false;
- bool stripLeadingSpace = true;
-
- forEachElement(textParagraphElement , parent) {
- if (textParagraphElement.localName() == "p" &&
- textParagraphElement.namespaceURI() == KoXmlNS::text) {
-
- // the text:a link could be located within a text:span element
- KoXmlElement textA = namedItemNSWithSpan(textParagraphElement, \
KoXmlNS::text, "a");
- if (!textA.isNull() && textA.hasAttributeNS(KoXmlNS::xlink, "href")) {
- QString link = textA.attributeNS(KoXmlNS::xlink, "href", QString());
- cellText = textA.text();
- setUserInput(cellText);
- hasRichText = false;
- lineCount = 0;
- // The value will be set later in loadOdf().
- if ((!link.isEmpty()) && (link[0] == '#'))
- link.remove(0, 1);
- setLink(link);
- // Abort here cause we can handle only either a link in a cell or \
(rich-)text but not both.
- break;
- }
-
- if (!cellText.isNull())
- cellText += '\n';
-
- ++lineCount;
- int textFragmentCount = 0;
-
- // Our text could contain formating for value or result of formula or a \
mix of
- // multiple text:span elements with text-nodes and line-break's.
- cellText += loadOdfCellTextNodes(textParagraphElement, \
&textFragmentCount, &lineCount, &hasRichText, &stripLeadingSpace);
-
- // If we got text from multiple different sources (e.g. from the text:p \
and a
- // child text:span) then we have very likely rich-text.
- if (!hasRichText)
- hasRichText = textFragmentCount >= 2;
- }
- }
-
- if (!cellText.isNull()) {
- if (hasRichText && !findDrawElements(parent)) {
- // for now we don't support richtext and embedded shapes in the same \
cell;
- // this is because they would currently be loaded twice, once by the \
KoTextLoader
- // and later properly by the cell itself
-
- Style style; style.setDefault();
- if (!cellStyleName.isEmpty()) {
- if (autoStyles.contains(cellStyleName))
- style.merge(autoStyles[cellStyleName]);
- else {
- const CustomStyle* namedStyle = \
sheet()->map()->styleManager()->style(cellStyleName);
- if (namedStyle)
- style.merge(*namedStyle);
- }
- }
-
- QTextCharFormat format = style.asCharFormat();
- ((KoCharacterStyle \
*)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
-
- QSharedPointer<QTextDocument> doc(new QTextDocument);
- KoTextDocument(doc.data()).setStyleManager(sheet()->map()->textStyleManager());
-
- Q_ASSERT(tableContext.shapeContext);
- KoTextLoader loader(*tableContext.shapeContext);
- QTextCursor cursor(doc.data());
- loader.loadBody(parent, cursor);
-
- setUserInput(doc->toPlainText());
- setRichText(doc);
- } else {
- setUserInput(cellText);
- }
- }
-
- // enable word wrapping if multiple lines of text have been found.
- if (lineCount >= 2) {
- Style newStyle;
- newStyle.setWrapText(true);
- setStyle(newStyle);
- }
-}
-
-void Cell::loadOdfObjects(const KoXmlElement &parent, OdfLoadingContext& \
tableContext, QList<ShapeLoadingData>& shapeData)
-{
- // Register additional attributes, that identify shapes anchored in cells.
- // Their dimensions need adjustment after all rows are loaded,
- // because the position of the end cell is not always known yet.
- KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
- KoXmlNS::table, "end-cell-address",
- "table:end-cell-address"));
- KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
- KoXmlNS::table, "end-x",
- "table:end-x"));
- KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
- KoXmlNS::table, "end-y",
- "table:end-y"));
-
- KoXmlElement element;
- forEachElement(element, parent) {
- if (element.namespaceURI() != KoXmlNS::draw)
- continue;
-
- if (element.localName() == "a") {
- // It may the case that the object(s) are embedded into a hyperlink so \
actions are done on
- // clicking it/them but since we do not supported \
objects-with-hyperlinks yet we just fetch
- // the inner elements and use them to at least create and show the \
objects (see bug 249862).
- KoXmlElement e;
- forEachElement(e, element) {
- if (e.namespaceURI() != KoXmlNS::draw)
- continue;
- ShapeLoadingData data = loadOdfObject(e, \
*tableContext.shapeContext);
- if (data.shape) {
- shapeData.append(data);
- }
- }
- } else {
- ShapeLoadingData data = loadOdfObject(element, \
*tableContext.shapeContext);
- if (data.shape) {
- shapeData.append(data);
- }
- }
- }
-}
-
-ShapeLoadingData Cell::loadOdfObject(const KoXmlElement &element, \
KoShapeLoadingContext &shapeContext)
-{
- ShapeLoadingData data;
- data.shape = 0;
- KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, \
shapeContext);
- if (!shape) {
- debugSheetsODF << "Unable to load shape with localName=" << \
element.localName();
- return data;
- }
-
- d->sheet->addShape(shape);
-
- // The position is relative to the upper left sheet corner until now. Move it.
- QPointF position = shape->position();
- // Remember how far we're off from the top-left corner of this cell
- double offsetX = position.x();
- double offsetY = position.y();
- for (int col = 1; col < column(); ++col)
- position += QPointF(d->sheet->columnFormat(col)->width(), 0.0);
- if (this->row() > 1)
- position += QPointF(0.0, d->sheet->rowFormats()->totalRowHeight(1, \
this->row() - 1));
- shape->setPosition(position);
-
- dynamic_cast<ShapeApplicationData*>(shape->applicationData())->setAnchoredToCell(true);
-
- // All three attributes are necessary for cell anchored shapes.
- // Otherwise, they are anchored in the sheet.
- if (!shape->hasAdditionalAttribute("table:end-cell-address") ||
- !shape->hasAdditionalAttribute("table:end-x") ||
- !shape->hasAdditionalAttribute("table:end-y")) {
- debugSheetsODF << "Not all attributes found, that are necessary for cell \
anchoring.";
- return data;
- }
-
- Region endCell(Region::loadOdf(shape->additionalAttribute("table:end-cell-address")),
- d->sheet->map(), d->sheet);
- if (!endCell.isValid() || !endCell.isSingular())
- return data;
-
- QString string = shape->additionalAttribute("table:end-x");
- if (string.isNull())
- return data;
- double endX = KoUnit::parseValue(string);
-
- string = shape->additionalAttribute("table:end-y");
- if (string.isNull())
- return data;
- double endY = KoUnit::parseValue(string);
-
- data.shape = shape;
- data.startCell = QPoint(column(), row());
- data.offset = QPointF(offsetX, offsetY);
- data.endCell = endCell;
- data.endPoint = QPointF(endX, endY);
-
- // The column dimensions are already the final ones, but not the row dimensions.
- // The default height is used for the not yet loaded rows.
- // TODO Stefan: Honor non-default row heights later!
- // subtract offset because the accumulated width and height we calculate below \
starts
- // at the top-left corner of this cell, but the shape can have an offset to that \
corner
- QSizeF size = QSizeF(endX - offsetX, endY - offsetY);
- for (int col = column(); col < endCell.firstRange().left(); ++col)
- size += QSizeF(d->sheet->columnFormat(col)->width(), 0.0);
- if (endCell.firstRange().top() > this->row())
- size += QSizeF(0.0, d->sheet->rowFormats()->totalRowHeight(this->row(), \
endCell.firstRange().top() - 1));
- shape->setSize(size);
-
- return data;
-}
-
bool Cell::load(const KoXmlElement & cell, int _xshift, int _yshift,
Paste::Mode mode, Paste::Operation op, bool paste)
{
diff --git a/sheets/Cell.h b/sheets/Cell.h
index de62343..c939f8b 100644
--- a/sheets/Cell.h
+++ b/sheets/Cell.h
@@ -49,10 +49,6 @@ class QPoint;
class QDate;
class KLocale;
-class KoXmlWriter;
-class KoGenStyles;
-class KoGenStyle;
-class KoShapeLoadingContext;
namespace Calligra
{
@@ -62,13 +58,10 @@ class Conditions;
class Database;
class Doc;
class Formula;
-class OdfLoadingContext;
-class OdfSavingContext;
class Sheet;
class Validity;
class Value;
class CellTest;
-struct ShapeLoadingData;
/**
* An accessor to the actual cell data.
@@ -361,22 +354,6 @@ public:
QDate toDate(const KoXmlElement &element);
/**
- * \ingroup OpenDocument
- * Loads a cell from an OASIS XML element.
- * @param element An OASIS XML element
- * @param tableContext The loading context assoiated with the XML element
- */
- bool loadOdf(const KoXmlElement& element, OdfLoadingContext& tableContext,
- const Styles& autoStyles, const QString& cellStyleName,
- QList<ShapeLoadingData>& shapeData);
-
- /**
- * \ingroup OpenDocument
- */
- bool saveOdf(int row, int column, int &repeated,
- OdfSavingContext& savingContext);
-
- /**
* Copies the format from \p cell .
*
* @see copyAll(Cell *cell)
@@ -596,26 +573,6 @@ public:
*/
bool compareData(const Cell& other) const;
-protected:
- /**
- * \ingroup OpenDocument
- * Load the text paragraphs from an OASIS XML cell description.
- * @param parent The DOM element representing the cell.
- */
- void loadOdfCellText(const KoXmlElement& parent, OdfLoadingContext& \
tableContext, const Styles& autoStyles, const QString& \
cellStyleName);
-
- /**
- * \ingroup OpenDocument
- */
- void loadOdfObjects(const KoXmlElement& e, OdfLoadingContext& tableContext, \
QList<ShapeLoadingData>& shapeData);
-
-
- /**
- * \ingroup OpenDocument
- */
- void saveOdfAnnotation(KoXmlWriter &xmlwriter);
-public:
- ShapeLoadingData loadOdfObject(const KoXmlElement& element, \
KoShapeLoadingContext& shapeContext); private:
friend class CellTest;
@@ -631,17 +588,6 @@ private:
* \ingroup NativeFormat
*/
bool saveCellResult(QDomDocument& doc, QDomElement& result, QString str);
-
- /**
- * \ingroup OpenDocument
- */
- void saveOdfValue(KoXmlWriter &xmlWriter);
-
- /**
- * \ingroup OpenDocument
- * @return the OASIS style's name
- */
- QString saveOdfCellStyle(KoGenStyle ¤tCellStyle, KoGenStyles &mainStyles);
};
inline uint qHash(const Cell& cell)
diff --git a/sheets/Style.cpp b/sheets/Style.cpp
index 68dcc9c..74b8f2b 100644
--- a/sheets/Style.cpp
+++ b/sheets/Style.cpp
@@ -355,6 +355,81 @@ void Style::loadOdfDataStyle(KoOdfStylesReader &stylesReader, \
const QString &sty }
}
+QString encodePen(const QPen & pen)
+{
+// debugSheets<<"encodePen( const QPen & pen ) :"<<pen;
+ // NOTE Stefan: QPen api docs:
+ // A line width of zero indicates a cosmetic pen. This means
+ // that the pen width is always drawn one pixel wide,
+ // independent of the transformation set on the painter.
+ QString s = QString("%1pt ").arg((pen.width() == 0) ? 1 : pen.width());
+ switch (pen.style()) {
+ case Qt::NoPen:
+ return "none";
+ case Qt::SolidLine:
+ s += "solid";
+ break;
+ case Qt::DashLine:
+ s += "dashed";
+ break;
+ case Qt::DotLine:
+ s += "dotted";
+ break;
+ case Qt::DashDotLine:
+ s += "dot-dash";
+ break;
+ case Qt::DashDotDotLine:
+ s += "dot-dot-dash";
+ break;
+ default: break;
+ }
+ //debugSheets << " encodePen :" << s;
+ if (pen.color().isValid()) {
+ s += ' ' + Style::colorName(pen.color());
+ }
+ return s;
+}
+
+QPen decodePen(const QString &border)
+{
+ QPen pen;
+ //string like "0.088cm solid #800000"
+ if (border.isEmpty() || border == "none" || border == "hidden") { // in fact no \
border + pen.setStyle(Qt::NoPen);
+ return pen;
+ }
+ //code from koborder, for the moment Calligra Sheets doesn't use koborder
+ // ## isn't it faster to use QStringList::split than parse it 3 times?
+ QString _width = border.section(' ', 0, 0);
+ QByteArray _style = border.section(' ', 1, 1).toLatin1();
+ QString _color = border.section(' ', 2, 2);
+
+ pen.setWidth((int)(KoUnit::parseValue(_width, 1.0)));
+
+ if (_style == "none")
+ pen.setStyle(Qt::NoPen);
+ else if (_style == "solid")
+ pen.setStyle(Qt::SolidLine);
+ else if (_style == "dashed")
+ pen.setStyle(Qt::DashLine);
+ else if (_style == "dotted")
+ pen.setStyle(Qt::DotLine);
+ else if (_style == "dot-dash")
+ pen.setStyle(Qt::DashDotLine);
+ else if (_style == "dot-dot-dash")
+ pen.setStyle(Qt::DashDotDotLine);
+ else
+ debugSheets << " style undefined :" << _style;
+
+ if (_color.isEmpty())
+ pen.setColor(QColor());
+ else
+ pen.setColor(QColor(_color));
+
+ return pen;
+}
+
+
void Style::loadOdfParagraphProperties(KoOdfStylesReader& stylesReader, const \
KoStyleStack& styleStack) {
Q_UNUSED(stylesReader);
@@ -455,7 +530,7 @@ void Style::loadOdfTableCellProperties(KoOdfStylesReader& \
stylesReader, const Ko }
if (styleStack.hasProperty(KoXmlNS::fo, "border")) {
str = styleStack.property(KoXmlNS::fo, "border");
- QPen pen = Odf::decodePen(str);
+ QPen pen = decodePen(str);
setLeftBorderPen(pen);
setTopBorderPen(pen);
setBottomBorderPen(pen);
@@ -463,27 +538,27 @@ void Style::loadOdfTableCellProperties(KoOdfStylesReader& \
stylesReader, const Ko }
if (styleStack.hasProperty(KoXmlNS::fo, "border-left")) {
str = styleStack.property(KoXmlNS::fo, "border-left");
- setLeftBorderPen(Odf::decodePen(str));
+ setLeftBorderPen(decodePen(str));
}
if (styleStack.hasProperty(KoXmlNS::fo, "border-right")) {
str = styleStack.property(KoXmlNS::fo, "border-right");
- setRightBorderPen(Odf::decodePen(str));
+ setRightBorderPen(decodePen(str));
}
if (styleStack.hasProperty(KoXmlNS::fo, "border-top")) {
str = styleStack.property(KoXmlNS::fo, "border-top");
- setTopBorderPen(Odf::decodePen(str));
+ setTopBorderPen(decodePen(str));
}
if (styleStack.hasProperty(KoXmlNS::fo, "border-bottom")) {
str = styleStack.property(KoXmlNS::fo, "border-bottom");
- setBottomBorderPen(Odf::decodePen(str));
+ setBottomBorderPen(decodePen(str));
}
if (styleStack.hasProperty(KoXmlNS::style, "diagonal-tl-br")) {
str = styleStack.property(KoXmlNS::style, "diagonal-tl-br");
- setFallDiagonalPen(Odf::decodePen(str));
+ setFallDiagonalPen(decodePen(str));
}
if (styleStack.hasProperty(KoXmlNS::style, "diagonal-bl-tr")) {
str = styleStack.property(KoXmlNS::style, "diagonal-bl-tr");
- setGoUpDiagonalPen(Odf::decodePen(str));
+ setGoUpDiagonalPen(decodePen(str));
}
if (styleStack.hasProperty(KoXmlNS::draw, "style-name") || \
styleStack.hasProperty(KoXmlNS::calligra, "fill-style-name")) { @@ -1378,25 +1453,25 \
@@ void Style::saveOdfStyle(const QSet<Key>& keysToStore, KoGenStyle &style, \
(leftBorderPen() == rightBorderPen()) && (leftBorderPen() == bottomBorderPen())) {
if (leftBorderPen().style() != Qt::NoPen)
- style.addProperty("fo:border", Odf::encodePen(leftBorderPen()));
+ style.addProperty("fo:border", encodePen(leftBorderPen()));
} else {
if (keysToStore.contains(LeftPen) && (leftBorderPen().style() != Qt::NoPen))
- style.addProperty("fo:border-left", Odf::encodePen(leftBorderPen()));
+ style.addProperty("fo:border-left", encodePen(leftBorderPen()));
if (keysToStore.contains(RightPen) && (rightBorderPen().style() != \
Qt::NoPen))
- style.addProperty("fo:border-right", Odf::encodePen(rightBorderPen()));
+ style.addProperty("fo:border-right", encodePen(rightBorderPen()));
if (keysToStore.contains(TopPen) && (topBorderPen().style() != Qt::NoPen))
- style.addProperty("fo:border-top", Odf::encodePen(topBorderPen()));
+ style.addProperty("fo:border-top", encodePen(topBorderPen()));
if (keysToStore.contains(BottomPen) && (bottomBorderPen().style() != \
Qt::NoPen))
- style.addProperty("fo:border-bottom", \
Odf::encodePen(bottomBorderPen())); + \
style.addProperty("fo:border-bottom", encodePen(bottomBorderPen())); }
if (keysToStore.contains(FallDiagonalPen) && (fallDiagonalPen().style() != \
Qt::NoPen)) {
- style.addProperty("style:diagonal-tl-br", \
Odf::encodePen(fallDiagonalPen())); + \
style.addProperty("style:diagonal-tl-br", encodePen(fallDiagonalPen())); }
if (keysToStore.contains(GoUpDiagonalPen) && (goUpDiagonalPen().style() != \
Qt::NoPen)) {
- style.addProperty("style:diagonal-bl-tr", \
Odf::encodePen(goUpDiagonalPen())); + \
style.addProperty("style:diagonal-bl-tr", encodePen(goUpDiagonalPen())); }
// font
diff --git a/sheets/Util.cpp b/sheets/Util.cpp
index f125782..903d641 100644
--- a/sheets/Util.cpp
+++ b/sheets/Util.cpp
@@ -321,80 +321,6 @@ QString Calligra::Sheets::Odf::convertRangeToRef(const QString & \
sheetName, cons
return sheetName + '.' + Cell::name(_area.left(), _area.top()) + ':' + sheetName \
+ '.' + Cell::name(_area.right(), _area.bottom()); }
-QString Calligra::Sheets::Odf::encodePen(const QPen & pen)
-{
-// debugSheets<<"encodePen( const QPen & pen ) :"<<pen;
- // NOTE Stefan: QPen api docs:
- // A line width of zero indicates a cosmetic pen. This means
- // that the pen width is always drawn one pixel wide,
- // independent of the transformation set on the painter.
- QString s = QString("%1pt ").arg((pen.width() == 0) ? 1 : pen.width());
- switch (pen.style()) {
- case Qt::NoPen:
- return "none";
- case Qt::SolidLine:
- s += "solid";
- break;
- case Qt::DashLine:
- s += "dashed";
- break;
- case Qt::DotLine:
- s += "dotted";
- break;
- case Qt::DashDotLine:
- s += "dot-dash";
- break;
- case Qt::DashDotDotLine:
- s += "dot-dot-dash";
- break;
- default: break;
- }
- //debugSheets << " encodePen :" << s;
- if (pen.color().isValid()) {
- s += ' ' + Style::colorName(pen.color());
- }
- return s;
-}
-
-QPen Calligra::Sheets::Odf::decodePen(const QString &border)
-{
- QPen pen;
- //string like "0.088cm solid #800000"
- if (border.isEmpty() || border == "none" || border == "hidden") { // in fact no \
border
- pen.setStyle(Qt::NoPen);
- return pen;
- }
- //code from koborder, for the moment Calligra Sheets doesn't use koborder
- // ## isn't it faster to use QStringList::split than parse it 3 times?
- QString _width = border.section(' ', 0, 0);
- QByteArray _style = border.section(' ', 1, 1).toLatin1();
- QString _color = border.section(' ', 2, 2);
-
- pen.setWidth((int)(KoUnit::parseValue(_width, 1.0)));
-
- if (_style == "none")
- pen.setStyle(Qt::NoPen);
- else if (_style == "solid")
- pen.setStyle(Qt::SolidLine);
- else if (_style == "dashed")
- pen.setStyle(Qt::DashLine);
- else if (_style == "dotted")
- pen.setStyle(Qt::DotLine);
- else if (_style == "dot-dash")
- pen.setStyle(Qt::DashDotLine);
- else if (_style == "dot-dot-dash")
- pen.setStyle(Qt::DashDotDotLine);
- else
- debugSheets << " style undefined :" << _style;
-
- if (_color.isEmpty())
- pen.setColor(QColor());
- else
- pen.setColor(QColor(_color));
-
- return pen;
-}
-
//Return true when it's a reference to cell from sheet.
bool Calligra::Sheets::Util::localReferenceAnchor(const QString &_ref)
{
diff --git a/sheets/Util.h b/sheets/Util.h
index 0a334e0..706c4c4 100644
--- a/sheets/Util.h
+++ b/sheets/Util.h
@@ -132,20 +132,6 @@ namespace Odf
{
/**
* \ingroup OpenDocument
- * Creates OpenDocument pen attributes of the QPen \p pen .
- * \return the OpenDocument pen attributes
- */
-QString encodePen(const QPen& pen);
-
-/**
- * \ingroup OpenDocument
- * Creates a QPen of OpenDocument pen attributes \p str .
- * \return the created QPen
- */
-QPen decodePen(const QString &str);
-
-/**
- * \ingroup OpenDocument
* Converts an OpenDocument representation of a formula to a localized formula.
* @param expr The expression to convert from OpenDocument format.
* @param locale The locale to which the expression should be converted.
diff --git a/sheets/odf/SheetsOdfCell.cpp b/sheets/odf/SheetsOdfCell.cpp
index ffc428a..da16d69 100644
--- a/sheets/odf/SheetsOdfCell.cpp
+++ b/sheets/odf/SheetsOdfCell.cpp
@@ -33,12 +33,946 @@
#include "SheetsOdf.h"
+#include <KoGenStyles.h>
+#include <KoShape.h>
+#include <KoShapeLoadingContext.h>
+#include <KoShapeRegistry.h>
+#include <KoStyleManager.h>
+#include <KoTextDocument.h>
+#include <KoTextLoader.h>
+#include <KoTextSharedLoadingData.h>
+#include <KoTextWriter.h>
+#include <KoUnit.h>
+#include <KoXmlNS.h>
+#include <KoXmlWriter.h>
+
+#include "Cell.h"
+#include "CellStorage.h"
+#include "Condition.h"
+#include "Map.h"
+#include "Sheet.h"
+#include "Util.h"
+#include "Value.h"
+#include "ValueFormatter.h"
+#include "GenValidationStyle.h"
+#include "OdfLoadingContext.h"
+#include "OdfSavingContext.h"
+#include "ShapeApplicationData.h"
+
// This file contains functionality to load/save a Cell
namespace Calligra {
namespace Sheets {
+namespace Odf {
+ bool loadCell(Cell *cell, const KoXmlElement& element, OdfLoadingContext& \
tableContext, + const Styles& autoStyles, const QString& cellStyleName,
+ QList<ShapeLoadingData>& shapeData);
+ bool saveCell(Cell *cell, int &repeated, OdfSavingContext& tableContext);
+
+ // cell loading - helper functions
+ void loadCellText(Cell *cell, const KoXmlElement& parent, OdfLoadingContext& \
tableContext, const Styles& autoStyles, const QString& cellStyleName); + QString \
loadCellTextNodes(Cell *cell, const KoXmlElement& element, int *textFragmentCount, \
int *lineCount, bool *hasRichText, bool *stripLeadingSpace); + void \
loadObjects(Cell *cell, const KoXmlElement &parent, OdfLoadingContext& tableContext, \
QList<ShapeLoadingData>& shapeData); + ShapeLoadingData loadObject(Cell *cell, \
const KoXmlElement &element, KoShapeLoadingContext &shapeContext); +
+ // cell saving - helper functions
+ QString saveCellStyle(Cell *cell, KoGenStyle ¤tCellStyle, KoGenStyles \
&mainStyles); + void saveCellAnnotation(Cell *cell, KoXmlWriter &xmlwriter);
+ void saveCellValue(Cell *cell, KoXmlWriter &xmlWriter);
+}
+
+// *************** Loading *****************
+bool Odf::loadCell(Cell *cell, const KoXmlElement& element, OdfLoadingContext& \
tableContext, + const Styles& autoStyles, const QString& cellStyleName,
+ QList<ShapeLoadingData>& shapeData)
+{
+ static const QString sFormula = QString::fromLatin1("formula");
+ static const QString sValidationName = \
QString::fromLatin1("validation-name"); + static const QString sValueType \
= QString::fromLatin1("value-type"); + static const QString sBoolean = \
QString::fromLatin1("boolean"); + static const QString sBooleanValue = \
QString::fromLatin1("boolean-value"); + static const QString sTrue = \
QString::fromLatin1("true"); + static const QString sFalse = \
QString::fromLatin1("false"); + static const QString sFloat = \
QString::fromLatin1("float"); + static const QString sValue = \
QString::fromLatin1("value"); + static const QString sCurrency = \
QString::fromLatin1("currency"); + static const QString sPercentage = \
QString::fromLatin1("percentage"); + static const QString sDate = \
QString::fromLatin1("date"); + static const QString sDateValue = \
QString::fromLatin1("date-value"); + static const QString sTime = \
QString::fromLatin1("time"); + static const QString sTimeValue = \
QString::fromLatin1("time-value"); + static const QString sString = \
QString::fromLatin1("string"); + static const QString sStringValue = \
QString::fromLatin1("string-value"); + static const QString sNumberColumnsSpanned \
= QString::fromLatin1("number-columns-spanned"); + static const QString \
sNumberRowsSpanned = QString::fromLatin1("number-rows-spanned"); + static const \
QString sAnnotation = QString::fromLatin1("annotation"); + static const \
QString sP = QString::fromLatin1("p"); +
+ static const QStringList formulaNSPrefixes = QStringList() << "oooc:" << "kspr:" \
<< "of:" << "msoxl:"; +
+ //Search and load each paragraph of text. Each paragraph is separated by a line \
break. + loadCellText(cell, element, tableContext, autoStyles, cellStyleName);
+
+ //
+ // formula
+ //
+ bool isFormula = false;
+ if (element.hasAttributeNS(KoXmlNS::table, sFormula)) {
+ isFormula = true;
+ QString oasisFormula(element.attributeNS(KoXmlNS::table, sFormula, \
QString())); + // debugSheetsODF << "cell:" << name() << "formula :" << \
oasisFormula; + // each spreadsheet application likes to safe formulas with a \
different namespace + // prefix, so remove all of them
+ QString namespacePrefix;
+ foreach(const QString &prefix, formulaNSPrefixes) {
+ if (oasisFormula.startsWith(prefix)) {
+ oasisFormula.remove(0, prefix.length());
+ namespacePrefix = prefix;
+ break;
+ }
+ }
+ oasisFormula = Odf::decodeFormula(oasisFormula, locale(), namespacePrefix);
+ cell->setUserInput(oasisFormula);
+ } else if (!userInput().isEmpty() && userInput().at(0) == '=') //prepend ' to \
the text to avoid = to be painted + \
cell->setUserInput(userInput().prepend('\'')); +
+ //
+ // validation
+ //
+ if (element.hasAttributeNS(KoXmlNS::table, sValidationName)) {
+ const QString validationName = element.attributeNS(KoXmlNS::table, \
sValidationName, QString()); + debugSheetsODF << "cell:" << name() << \
sValidationName << validationName; + Validity validity;
+#warning use new odf
+ validity.loadOdfValidation(this, validationName, tableContext);
+ if (!validity.isEmpty())
+ setValidity(validity);
+ }
+
+ //
+ // value type
+ //
+ if (element.hasAttributeNS(KoXmlNS::office, sValueType)) {
+ const QString valuetype = element.attributeNS(KoXmlNS::office, sValueType, \
QString()); + // debugSheetsODF << "cell:" << name() << "value-type:" << \
valuetype; + if (valuetype == sBoolean) {
+ const QString val = element.attributeNS(KoXmlNS::office, sBooleanValue, \
QString()).toLower(); + if ((val == sTrue) || (val == sFalse))
+ cell->setValue(Value(val == sTrue));
+ }
+
+ // integer and floating-point value
+ else if (valuetype == sFloat) {
+ bool ok = false;
+ Value value(element.attributeNS(KoXmlNS::office, sValue, \
QString()).toDouble(&ok)); + if (ok) {
+ value.setFormat(Value::fmt_Number);
+ cell->setValue(value);
+#if 0
+ Style style;
+ style.setFormatType(Format::Number);
+ cell->setStyle(style);
+#endif
+ }
+ // always set the userInput to the actual value read from the cell, and \
not whatever happens to be set as text, as the textual representation of a value may \
be less accurate than the value itself + if (!isFormula)
+ cell->setUserInput(cell->sheet()->map()->converter()->asString(value).asString());
+ }
+
+ // currency value
+ else if (valuetype == sCurrency) {
+ bool ok = false;
+ Value value(element.attributeNS(KoXmlNS::office, sValue, \
QString()).toDouble(&ok)); + if (ok) {
+ value.setFormat(Value::fmt_Money);
+ cell->setValue(value);
+
+ Currency currency;
+ if (element.hasAttributeNS(KoXmlNS::office, sCurrency)) {
+ currency = Currency(element.attributeNS(KoXmlNS::office, \
sCurrency, QString())); + }
+ /* TODO: somehow make this work again, all setStyle calls here will \
be overwritten by cell styles later + if( style.isEmpty() ) {
+ Style style;
+ style.setCurrency(currency);
+ setStyle(style);
+ } */
+ }
+ } else if (valuetype == sPercentage) {
+ bool ok = false;
+ Value value(element.attributeNS(KoXmlNS::office, sValue, \
QString()).toDouble(&ok)); + if (ok) {
+ value.setFormat(Value::fmt_Percent);
+ cell->setValue(value);
+ if (!isFormula && cell->userInput().isEmpty())
+ \
cell->setUserInput(cell->sheet()->map()->converter()->asString(value).asString()); \
+// FIXME Stefan: Should be handled by Value::Format. Verify and remove! +#if 0
+ Style style;
+ style.setFormatType(Format::Percentage);
+ setStyle(style);
+#endif
+ }
+ } else if (valuetype == sDate) {
+ QString value = element.attributeNS(KoXmlNS::office, sDateValue, \
QString()); +
+ // "1980-10-15" or "2001-01-01T19:27:41"
+ int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0;
+ bool hasTime = false;
+ bool ok = false;
+
+ int p1 = value.indexOf('-');
+ if (p1 > 0) {
+ year = value.left(p1).toInt(&ok);
+ if (ok) {
+ int p2 = value.indexOf('-', ++p1);
+ month = value.mid(p1, p2 - p1).toInt(&ok);
+ if (ok) {
+ // the date can optionally have a time attached
+ int p3 = value.indexOf('T', ++p2);
+ if (p3 > 0) {
+ hasTime = true;
+ day = value.mid(p2, p3 - p2).toInt(&ok);
+ if (ok) {
+ int p4 = value.indexOf(':', ++p3);
+ hours = value.mid(p3, p4 - p3).toInt(&ok);
+ if (ok) {
+ int p5 = value.indexOf(':', ++p4);
+ minutes = value.mid(p4, p5 - p4).toInt(&ok);
+ if (ok)
+ seconds = value.right(value.length() - p5 - \
1).toInt(&ok); + }
+ }
+ } else {
+ day = value.right(value.length() - p2).toInt(&ok);
+ }
+ }
+ }
+ }
+
+ if (ok) {
+ if (hasTime)
+ cell->setValue(Value(QDateTime(QDate(year, month, day), \
QTime(hours, minutes, seconds)), cell->sheet()->map()->calculationSettings())); + \
else + cell->setValue(Value(QDate(year, month, day), \
cell->sheet()->map()->calculationSettings())); +// FIXME Stefan: Should be handled by \
Value::Format. Verify and remove! +//Sebsauer: Fixed now. Value::Format handles it \
correct. +#if 0
+ Style s;
+ s.setFormatType(Format::ShortDate);
+ setStyle(s);
+#endif
+ // debugSheetsODF << "cell:" << name() << "Type: date, value:" << \
value << "Date:" << year << " -" << month << " -" << day; + }
+ } else if (valuetype == sTime) {
+ QString value = element.attributeNS(KoXmlNS::office, sTimeValue, \
QString()); +
+ // "PT15H10M12S"
+ int hours = 0, minutes = 0, seconds = 0;
+ int l = value.length();
+ QString num;
+ bool ok = false;
+ for (int i = 0; i < l; ++i) {
+ if (value[i].isNumber()) {
+ num += value[i];
+ continue;
+ } else if (value[i] == 'H')
+ hours = num.toInt(&ok);
+ else if (value[i] == 'M')
+ minutes = num.toInt(&ok);
+ else if (value[i] == 'S')
+ seconds = num.toInt(&ok);
+ else
+ continue;
+ //debugSheetsODF << "Num:" << num;
+ num.clear();
+ if (!ok)
+ break;
+ }
+
+ if (ok) {
+ // Value kval( timeToNum( hours, minutes, seconds ) );
+ // cell->setValue( kval );
+ cell->setValue(Value(QTime(hours % 24, minutes, seconds)));
+// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
+#if 0
+ Style style;
+ style.setFormatType(Format::Time);
+ setStyle(style);
+#endif
+ // debugSheetsODF << "cell:" << name() << "Type: time:" << value << \
"Hours:" << hours << "," << minutes << "," << seconds; + }
+ } else if (valuetype == sString) {
+ if (element.hasAttributeNS(KoXmlNS::office, sStringValue)) {
+ QString value = element.attributeNS(KoXmlNS::office, sStringValue, \
QString()); + cell->setValue(Value(value));
+ } else {
+ // use the paragraph(s) read in before
+ cell->setValue(Value(cell->userInput()));
+ }
+// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
+#if 0
+ Style style;
+ style.setFormatType(Format::Text);
+ setStyle(style);
+#endif
+ } else {
+ // debugSheetsODF << "cell:" << name() << " Unknown type. Parsing user \
input."; + // Set the value by parsing the user input.
+ cell->parseUserInput(cell->userInput());
+ }
+ } else { // no value-type attribute
+ // debugSheetsODF << "cell:" << name() << " No value type specified. \
Parsing user input."; + // Set the value by parsing the user input.
+ cell->parseUserInput(cell->userInput());
+ }
+
+ //
+ // merged cells ?
+ //
+ int colSpan = 1;
+ int rowSpan = 1;
+ if (element.hasAttributeNS(KoXmlNS::table, sNumberColumnsSpanned)) {
+ bool ok = false;
+ int span = element.attributeNS(KoXmlNS::table, sNumberColumnsSpanned, \
QString()).toInt(&ok); + if (ok) colSpan = span;
+ }
+ if (element.hasAttributeNS(KoXmlNS::table, sNumberRowsSpanned)) {
+ bool ok = false;
+ int span = element.attributeNS(KoXmlNS::table, sNumberRowsSpanned, \
QString()).toInt(&ok); + if (ok) rowSpan = span;
+ }
+ if (colSpan > 1 || rowSpan > 1)
+ mergeCells(d->column, d->row, colSpan - 1, rowSpan - 1);
+
+ //
+ // cell comment/annotation
+ //
+ KoXmlElement annotationElement = KoXml::namedItemNS(element, KoXmlNS::office, \
sAnnotation); + if (!annotationElement.isNull()) {
+ QString comment;
+ KoXmlNode node = annotationElement.firstChild();
+ while (!node.isNull()) {
+ KoXmlElement commentElement = node.toElement();
+ if (!commentElement.isNull())
+ if (commentElement.localName() == sP && \
commentElement.namespaceURI() == KoXmlNS::text) { + if \
(!comment.isEmpty()) comment.append('\n'); + \
comment.append(commentElement.text()); + }
+
+ node = node.nextSibling();
+ }
+ if (!comment.isEmpty())
+ setComment(comment);
+ }
+
+ loadObjects(cell, element, tableContext, shapeData);
+
+ return true;
+}
+
+bool Odf::saveCell(Cell *cell, int &repeated, OdfSavingContext& tableContext)
+{
+ KoXmlWriter & xmlwriter = tableContext.shapeContext.xmlWriter();
+ KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
+
+ int row = cell->row();
+ int column = cell->column();
+
+ // see: OpenDocument, 8.1.3 Table Cell
+ if (!isPartOfMerged())
+ xmlwriter.startElement("table:table-cell");
+ else
+ xmlwriter.startElement("table:covered-table-cell");
+#if 0
+ //add font style
+ QFont font;
+ Value const value(cell.value());
+ if (!cell.isDefault()) {
+ font = cell.format()->textFont(i, row);
+ m_styles.addFont(font);
+
+ if (cell.format()->hasProperty(Style::SComment))
+ hasComment = true;
+ }
+#endif
+ // NOTE save the value before the style as long as the Formatter does not work \
correctly + if (cell->link().isEmpty())
+ saveCellValue(cell, xmlwriter);
+
+ const Style cellStyle = cell->style();
+
+ // Either there's no column and row default and the style's not the default \
style, + // or the style is different to one of them. The row default takes \
precedence. + if ((!tableContext.rowDefaultStyles.contains(row) &&
+ !tableContext.columnDefaultStyles.contains(column) &&
+ !(cellStyle.isDefault() && conditions().isEmpty())) ||
+ (tableContext.rowDefaultStyles.contains(row) && \
tableContext.rowDefaultStyles[row] != cellStyle) || + \
(tableContext.columnDefaultStyles.contains(column) && \
tableContext.columnDefaultStyles[column] != cellStyle)) { + KoGenStyle \
currentCellStyle; // the type determined in saveCellStyle + QString styleName \
= saveCellStyle(currentCellStyle, mainStyles); + // skip 'table:style-name' \
attribute for the default style + if (!currentCellStyle.isDefaultStyle()) {
+ if (!styleName.isEmpty())
+ xmlwriter.addAttribute("table:style-name", styleName);
+ }
+ }
+
+ // group empty cells with the same style
+ const QString comment = cell->comment();
+ if (isEmpty() && comment.isEmpty() && !isPartOfMerged() && !doesMergeCells() &&
+ !tableContext.cellHasAnchoredShapes(cell->sheet(), row, column)) {
+ bool refCellIsDefault = isDefault();
+ int j = column + 1;
+ Cell nextCell = cell->sheet()->cellStorage()->nextInRow(column, row);
+ while (!nextCell.isNull()) {
+ // if
+ // the next cell is not the adjacent one
+ // or
+ // the next cell is not empty
+ if (nextCell.column() != j || (!nextCell.isEmpty() || \
tableContext.cellHasAnchoredShapes(cell->sheet(), row, column))) { + \
if (refCellIsDefault) { + // if the origin cell was a default \
cell, + // we count the default cells
+ repeated = nextCell.column() - j + 1;
+
+ // check if any of the empty/default cells we skipped contained \
anchored shapes + int shapeColumn = \
tableContext.nextAnchoredShape(cell->sheet(), row, column); + if \
(shapeColumn) { + repeated = qMin(repeated, shapeColumn - \
column); + }
+ }
+ // otherwise we just stop here to process the adjacent
+ // cell in the next iteration of the outer loop
+ // (in saveCells)
+ break;
+ }
+
+ if (nextCell.isPartOfMerged() || nextCell.doesMergeCells() ||
+ !nextCell.comment().isEmpty() || \
tableContext.cellHasAnchoredShapes(cell->sheet(), row, nextCell.column()) || + \
!(nextCell.style() == cellStyle && nextCell.conditions() == conditions())) { + \
break; + }
+ ++repeated;
+ // get the next cell and set the index to the adjacent cell
+ nextCell = cell->sheet()->cellStorage()->nextInRow(j++, row);
+ }
+ //debugSheetsODF << "Odf::saveCell: empty cell in column" << column
+ //<< "repeated" << repeated << "time(s)" << endl;
+
+ if (repeated > 1)
+ xmlwriter.addAttribute("table:number-columns-repeated", \
QString::number(repeated)); + }
+
+ Validity validity = cell->validity();
+ if (!validity.isEmpty()) {
+ GenValidationStyle styleVal(&validity, cell->sheet()->map()->converter());
+ xmlwriter.addAttribute("table:validation-name", \
tableContext.valStyle.insert(styleVal)); + }
+ if (cell->isFormula()) {
+ //debugSheetsODF <<"Formula found";
+ QString formula = Odf::encodeFormula(userInput(), locale());
+ xmlwriter.addAttribute("table:formula", formula);
+ }
+
+ if (doesMergeCells()) {
+ int colSpan = mergedXCells() + 1;
+ int rowSpan = mergedYCells() + 1;
+
+ if (colSpan > 1)
+ xmlwriter.addAttribute("table:number-columns-spanned", \
QString::number(colSpan)); +
+ if (rowSpan > 1)
+ xmlwriter.addAttribute("table:number-rows-spanned", \
QString::number(rowSpan)); + }
+
+ saveCellAnnotation(cell, xmlwriter);
+
+ if (!cell->isFormula() && !link().isEmpty()) {
+ //debugSheetsODF<<"Link found";
+ xmlwriter.startElement("text:p");
+ xmlwriter.startElement("text:a");
+ const QString url = link();
+ //Reference cell is started by '#'
+ if (Util::localReferenceAnchor(url))
+ xmlwriter.addAttribute("xlink:href", ('#' + url));
+ else
+ xmlwriter.addAttribute("xlink:href", url);
+ xmlwriter.addAttribute("xlink:type", "simple");
+ xmlwriter.addTextNode(userInput());
+ xmlwriter.endElement();
+ xmlwriter.endElement();
+ }
+
+ if (!isEmpty() && link().isEmpty()) {
+ QSharedPointer<QTextDocument> doc = richText();
+ if (doc) {
+ QTextCharFormat format = style().asCharFormat();
+ ((KoCharacterStyle \
*)cell->sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
+
+ KoTextWriter writer(tableContext.shapeContext);
+
+ writer.write(doc.data(), 0);
+ } else {
+ xmlwriter.startElement("text:p");
+ xmlwriter.addTextNode(displayText().toUtf8());
+ xmlwriter.endElement();
+ }
+ }
+
+ // flake
+ // Save shapes that are anchored to this cell.
+ // see: OpenDocument, 2.3.1 Text Documents
+ // see: OpenDocument, 9.2 Drawing Shapes
+ if (tableContext.cellHasAnchoredShapes(cell->sheet(), row, column)) {
+ const QList<KoShape*> shapes = \
tableContext.cellAnchoredShapes(cell->sheet(), row, column); + for (int i = 0; \
i < shapes.count(); ++i) { + KoShape* const shape = shapes[i];
+ const QPointF bottomRight = shape->boundingRect().bottomRight();
+ qreal endX = 0.0;
+ qreal endY = 0.0;
+ const int scol = cell->sheet()->leftColumn(bottomRight.x(), endX);
+ const int srow = cell->sheet()->topRow(bottomRight.y(), endY);
+ qreal offsetX = cell->sheet()->columnPosition(column);
+ qreal offsetY = cell->sheet()->rowPosition(row);
+ tableContext.shapeContext.addShapeOffset(shape, \
QTransform::fromTranslate(-offsetX, -offsetY)); +#warning use new odf
+ shape->setAdditionalAttribute("table:end-cell-address", \
Region(QPoint(scol, srow)).saveOdf()); + \
shape->setAdditionalAttribute("table:end-x", QString::number(bottomRight.x() - endX) \
+ "pt"); + shape->setAdditionalAttribute("table:end-y", \
QString::number(bottomRight.y() - endY) + "pt"); + \
shape->saveOdf(tableContext.shapeContext); + \
shape->removeAdditionalAttribute("table:end-cell-address"); + \
shape->removeAdditionalAttribute("table:end-x"); + \
shape->removeAdditionalAttribute("table:end-y"); + \
tableContext.shapeContext.removeShapeOffset(shape); + }
+ }
+
+ xmlwriter.endElement();
+ return true;
+}
+
+
+// loading - helper functions
+
+QString Odf::loadCellTextNodes(Cell *cell, const KoXmlElement& element, int \
*textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace) +{
+ QString cellText;
+ bool countedOwnFragments = false;
+ bool prevWasText = false;
+ for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ if (n.isText()) {
+ prevWasText = true;
+ QString t = KoTextLoader::normalizeWhitespace(n.toText().data(), \
*stripLeadingSpace); + if (!t.isEmpty()) {
+ *stripLeadingSpace = t[t.length() - 1].isSpace();
+ cellText += t;
+ if (!countedOwnFragments) {
+ // We only count the number of different parent elements which \
have text. That is + // so cause different parent-elements may \
mean different styles which means + // rich-text while the same \
parent element means the same style so we can easily + // put them \
together into one string. + countedOwnFragments = true;
+ ++(*textFragmentCount);
+ }
+ }
+ } else {
+ KoXmlElement e = n.toElement();
+ if (!e.isNull()) {
+ if (prevWasText && !cellText.isEmpty() && cellText[cellText.length() \
- 1].isSpace()) { + // A trailing space of the cellText collected \
so far needs to be preserved when + // more text-nodes within the \
same parent follow but if an element like e.g. + // text:s follows \
then a trailing space needs to be removed. + cellText.chop(1);
+ }
+ prevWasText = false;
+
+ // We can optimize some elements like text:s (space), text:tab \
(tabulator) and + // text:line-break (new-line) to not produce \
rich-text but add the equivalent + // for them in plain-text.
+ const bool isTextNs = e.namespaceURI() == KoXmlNS::text;
+ if (isTextNs && e.localName() == "s") {
+ const int howmany = qMax(1, e.attributeNS(KoXmlNS::text, "c", \
QString()).toInt()); + cellText += QString().fill(32, howmany);
+ } else if (isTextNs && e.localName() == "tab") {
+ cellText += '\t';
+ } else if (isTextNs && e.localName() == "line-break") {
+ cellText += '\n';
+ ++(*lineCount);
+ } else if (isTextNs && e.localName() == "span") {
+ // Nested span-elements means recursive evaluation.
+ cellText += loadCellTextNodes(cell, e, textFragmentCount, \
lineCount, hasRichText, stripLeadingSpace); + } else if (!isTextNs ||
+ ( e.localName() != "annotation" &&
+ e.localName() != "bookmark" &&
+ e.localName() != "meta" &&
+ e.localName() != "tag" )) {
+ // Seems we have an element we cannot easily translate to a \
string what + // means it's all rich-text now.
+ *hasRichText = true;
+ }
+ }
+ }
+ }
+ return cellText;
+}
+
+// recursively goes through all children of parent and returns true if there is any \
element +// in the draw: namespace in this subtree
+static bool findDrawElements(const KoXmlElement& parent)
+{
+ KoXmlElement element;
+ forEachElement(element , parent) {
+ if (element.namespaceURI() == KoXmlNS::draw)
+ return true;
+ if (findDrawElements(element))
+ return true;
+ }
+ return false;
+}
+
+// Similar to KoXml::namedItemNS except that children of span tags will be evaluated \
too. +static KoXmlElement namedItemNSWithSpan(const KoXmlNode& node, const QString \
&nsURI, const QString &localName) +{
+ KoXmlNode n = node.firstChild();
+ for (; !n.isNull(); n = n.nextSibling()) {
+ if (n.isElement()) {
+ if (n.localName() == localName && n.namespaceURI() == nsURI) {
+ return n.toElement();
+ }
+ if (n.localName() == "span" && n.namespaceURI() == nsURI) {
+ KoXmlElement e = KoXml::namedItemNS(n, nsURI, localName); // not \
recursive + if (!e.isNull()) {
+ return e;
+ }
+ }
+ }
+ }
+ return KoXmlElement();
+}
+
+void Odf::loadCellText(Cell *cell, const KoXmlElement& parent, OdfLoadingContext& \
tableContext, const Styles& autoStyles, const QString& cellStyleName) +{
+ //Search and load each paragraph of text. Each paragraph is separated by a line \
break + KoXmlElement textParagraphElement;
+ QString cellText;
+
+ int lineCount = 0;
+ bool hasRichText = false;
+ bool stripLeadingSpace = true;
+
+ forEachElement(textParagraphElement , parent) {
+ if (textParagraphElement.localName() == "p" &&
+ textParagraphElement.namespaceURI() == KoXmlNS::text) {
+
+ // the text:a link could be located within a text:span element
+ KoXmlElement textA = namedItemNSWithSpan(textParagraphElement, \
KoXmlNS::text, "a"); + if (!textA.isNull() && \
textA.hasAttributeNS(KoXmlNS::xlink, "href")) { + QString link = \
textA.attributeNS(KoXmlNS::xlink, "href", QString()); + cellText = \
textA.text(); + cell->setUserInput(cellText);
+ hasRichText = false;
+ lineCount = 0;
+ // The value will be set later in loadOdf().
+ if ((!link.isEmpty()) && (link[0] == '#'))
+ link.remove(0, 1);
+ cell->setLink(link);
+ // Abort here cause we can handle only either a link in a cell or \
(rich-)text but not both. + break;
+ }
+
+ if (!cellText.isNull())
+ cellText += '\n';
+
+ ++lineCount;
+ int textFragmentCount = 0;
+
+ // Our text could contain formating for value or result of formula or a \
mix of + // multiple text:span elements with text-nodes and line-break's.
+ cellText += loadCellTextNodes(cell, textParagraphElement, \
&textFragmentCount, &lineCount, &hasRichText, &stripLeadingSpace); +
+ // If we got text from multiple different sources (e.g. from the text:p \
and a + // child text:span) then we have very likely rich-text.
+ if (!hasRichText)
+ hasRichText = textFragmentCount >= 2;
+ }
+ }
+
+ if (!cellText.isNull()) {
+ if (hasRichText && !findDrawElements(parent)) {
+ // for now we don't support richtext and embedded shapes in the same \
cell; + // this is because they would currently be loaded twice, once by \
the KoTextLoader + // and later properly by the cell itself
+
+ Style style; style.setDefault();
+ if (!cellStyleName.isEmpty()) {
+ if (autoStyles.contains(cellStyleName))
+ style.merge(autoStyles[cellStyleName]);
+ else {
+ const CustomStyle* namedStyle = \
cell->sheet()->map()->styleManager()->style(cellStyleName); + if \
(namedStyle) + style.merge(*namedStyle);
+ }
+ }
+
+ QTextCharFormat format = style.asCharFormat();
+ ((KoCharacterStyle \
*)cell->sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
+
+ QSharedPointer<QTextDocument> doc(new QTextDocument);
+ KoTextDocument(doc.data()).setStyleManager(cell->sheet()->map()->textStyleManager());
+
+ Q_ASSERT(tableContext.shapeContext);
+ KoTextLoader loader(*tableContext.shapeContext);
+ QTextCursor cursor(doc.data());
+ loader.loadBody(parent, cursor);
+
+ cell->setUserInput(doc->toPlainText());
+ cell->setRichText(doc);
+ } else {
+ cell->setUserInput(cellText);
+ }
+ }
+
+ // enable word wrapping if multiple lines of text have been found.
+ if (lineCount >= 2) {
+ Style newStyle;
+ newStyle.setWrapText(true);
+ setStyle(newStyle);
+ }
+}
+
+void Odf::loadObjects(Cell *cell, const KoXmlElement &parent, OdfLoadingContext& \
tableContext, QList<ShapeLoadingData>& shapeData) +{
+ // Register additional attributes, that identify shapes anchored in cells.
+ // Their dimensions need adjustment after all rows are loaded,
+ // because the position of the end cell is not always known yet.
+ KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
+ KoXmlNS::table, "end-cell-address",
+ "table:end-cell-address"));
+ KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
+ KoXmlNS::table, "end-x",
+ "table:end-x"));
+ KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
+ KoXmlNS::table, "end-y",
+ "table:end-y"));
+
+ KoXmlElement element;
+ forEachElement(element, parent) {
+ if (element.namespaceURI() != KoXmlNS::draw)
+ continue;
+
+ if (element.localName() == "a") {
+ // It may the case that the object(s) are embedded into a hyperlink so \
actions are done on + // clicking it/them but since we do not supported \
objects-with-hyperlinks yet we just fetch + // the inner elements and use \
them to at least create and show the objects (see bug 249862). + \
KoXmlElement e; + forEachElement(e, element) {
+ if (e.namespaceURI() != KoXmlNS::draw)
+ continue;
+ ShapeLoadingData data = loadObject(cell, e, \
*tableContext.shapeContext); + if (data.shape) {
+ shapeData.append(data);
+ }
+ }
+ } else {
+ ShapeLoadingData data = loadObject(cell, element, \
*tableContext.shapeContext); + if (data.shape) {
+ shapeData.append(data);
+ }
+ }
+ }
+}
+
+ShapeLoadingData Odf::loadObject(Cell *cell, const KoXmlElement &element, \
KoShapeLoadingContext &shapeContext) +{
+ ShapeLoadingData data;
+ data.shape = 0;
+ KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, \
shapeContext); + if (!shape) {
+ debugSheetsODF << "Unable to load shape with localName=" << \
element.localName(); + return data;
+ }
+
+ cell->sheet()->addShape(shape);
+
+ // The position is relative to the upper left sheet corner until now. Move it.
+ QPointF position = shape->position();
+ // Remember how far we're off from the top-left corner of this cell
+ double offsetX = position.x();
+ double offsetY = position.y();
+ for (int col = 1; col < column(); ++col)
+ position += QPointF(cell->sheet()->columnFormat(col)->width(), 0.0);
+ if (cell->row() > 1)
+ position += QPointF(0.0, cell->sheet()->rowFormats()->totalRowHeight(1, \
cell->row() - 1)); + shape->setPosition(position);
+
+ dynamic_cast<ShapeApplicationData*>(shape->applicationData())->setAnchoredToCell(true);
+
+ // All three attributes are necessary for cell anchored shapes.
+ // Otherwise, they are anchored in the sheet.
+ if (!shape->hasAdditionalAttribute("table:end-cell-address") ||
+ !shape->hasAdditionalAttribute("table:end-x") ||
+ !shape->hasAdditionalAttribute("table:end-y")) {
+ debugSheetsODF << "Not all attributes found, that are necessary for cell \
anchoring."; + return data;
+ }
+
+#warning use new odf
+ Region endCell(Region::loadOdf(shape->additionalAttribute("table:end-cell-address")),
+ cell->sheet()->map(), cell->sheet());
+ if (!endCell.isValid() || !endCell.isSingular())
+ return data;
+
+ QString string = shape->additionalAttribute("table:end-x");
+ if (string.isNull())
+ return data;
+ double endX = KoUnit::parseValue(string);
+
+ string = shape->additionalAttribute("table:end-y");
+ if (string.isNull())
+ return data;
+ double endY = KoUnit::parseValue(string);
+
+ data.shape = shape;
+ data.startCell = QPoint(column(), row());
+ data.offset = QPointF(offsetX, offsetY);
+ data.endCell = endCell;
+ data.endPoint = QPointF(endX, endY);
+
+ // The column dimensions are already the final ones, but not the row dimensions.
+ // The default height is used for the not yet loaded rows.
+ // TODO Stefan: Honor non-default row heights later!
+ // subtract offset because the accumulated width and height we calculate below \
starts + // at the top-left corner of this cell, but the shape can have an offset \
to that corner + QSizeF size = QSizeF(endX - offsetX, endY - offsetY);
+ for (int col = column(); col < endCell.firstRange().left(); ++col)
+ size += QSizeF(cell->sheet()->columnFormat(col)->width(), 0.0);
+ if (endCell.firstRange().top() > cell->row())
+ size += QSizeF(0.0, cell->sheet()->rowFormats()->totalRowHeight(cell->row(), \
endCell.firstRange().top() - 1)); + shape->setSize(size);
+
+ return data;
+}
+
+
+// saving - helper functions
+
+void Odf::saveCellAnnotation(Cell *cell, KoXmlWriter &xmlwriter)
+{
+ const QString comment = cell->comment();
+ if (comment.isEmpty()) return;
+
+ //<office:annotation draw:style-name="gr1" draw:text-style-name="P1" \
svg:width="2.899cm" svg:height="2.691cm" svg:x="2.858cm" svg:y="0.001cm" \
draw:caption-point-x="-2.858cm" draw:caption-point-y="-0.001cm"> + \
xmlwriter.startElement("office:annotation"); + const QStringList text = \
comment.split('\n', QString::SkipEmptyParts); + for (QStringList::ConstIterator it \
= text.begin(); it != text.end(); ++it) { + xmlwriter.startElement("text:p");
+ xmlwriter.addTextNode(*it);
+ xmlwriter.endElement();
+ }
+ xmlwriter.endElement();
+}
+
+
+QString Odf::saveCellStyle(Cell *cell, KoGenStyle ¤tCellStyle, KoGenStyles \
&mainStyles) +{
+ const Conditions conditions = cell->conditions();
+ if (!conditions.isEmpty()) {
+ // this has to be an automatic style
+ currentCellStyle = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell");
+#warning use new odf
+ conditions.saveOdfConditions(currentCellStyle, \
cell->sheet()->map()->converter()); + }
+#warning TODO new style odf
+ return style().saveOdf(currentCellStyle, mainStyles, \
cell->sheet()->map()->styleManager()); +}
+
+void Odf::saveCellValue(Cell *cell, KoXmlWriter &xmlWriter)
+{
+ Value value = cell->value();
+ // Determine the format that we will be storing.
+ // This is usually the format that is actually shown - doing so mixes style and \
content, but that's how + // LO does it, so we need to stay compatible
+ Format::Type shownFormat = style().formatType();
+ if (shownFormat == Format::Generic)
+ shownFormat = cell->sheet()->map()->formatter()->determineFormatting(value, \
shownFormat); + Value::Format saveFormat = Value::fmt_None;
+ Value::Format valueFormat = value.format();
+ if (valueFormat == Value::fmt_Boolean)
+ saveFormat = Value::fmt_Boolean;
+ else if (valueFormat == Value::fmt_String) // if it's a text, it needs to be \
stored as a text + saveFormat = Value::fmt_String;
+ else if (Format::isDate(shownFormat))
+ saveFormat = Value::fmt_Date;
+ else if (Format::isTime(shownFormat))
+ saveFormat = Value::fmt_Time;
+ else if (Format::isNumber(shownFormat))
+ saveFormat = Value::fmt_Number;
+ else if (Format::isMoney(shownFormat))
+ saveFormat = Value::fmt_Money;
+ else if (shownFormat == Format::Percentage)
+ saveFormat = Value::fmt_Percent;
+ else if (shownFormat == Format::Text)
+ saveFormat = Value::fmt_String;
+ else if (shownFormat == Format::Custom)
+ saveFormat = valueFormat;
+
+ switch (saveFormat) {
+ case Value::fmt_None: break; //NOTHING HERE
+ case Value::fmt_Boolean: {
+ xmlWriter.addAttribute("office:value-type", "boolean");
+ xmlWriter.addAttribute("office:boolean-value", (value.asBoolean() ? "true" : \
"false")); + break;
+ }
+ case Value::fmt_Number: {
+ xmlWriter.addAttribute("office:value-type", "float");
+ if (value.isInteger())
+ xmlWriter.addAttribute("office:value", \
QString::number(value.asInteger())); + else
+ xmlWriter.addAttribute("office:value", \
QString::number(numToDouble(value.asFloat()), 'g', DBL_DIG)); + break;
+ }
+ case Value::fmt_Percent: {
+ xmlWriter.addAttribute("office:value-type", "percentage");
+ xmlWriter.addAttribute("office:value", QString::number((double) \
numToDouble(value.asFloat()))); + break;
+ }
+ case Value::fmt_Money: {
+ xmlWriter.addAttribute("office:value-type", "currency");
+ const Style style = cell->style();
+ if (style.hasAttribute(Style::CurrencyFormat)) {
+ Currency currency = style.currency();
+ xmlWriter.addAttribute("office:currency", currency.code());
+ }
+ xmlWriter.addAttribute("office:value", QString::number((double) \
numToDouble(value.asFloat()))); + break;
+ }
+ case Value::fmt_DateTime: break; //NOTHING HERE
+ case Value::fmt_Date: {
+ xmlWriter.addAttribute("office:value-type", "date");
+ xmlWriter.addAttribute("office:date-value",
+ \
value.asDate(cell->sheet()->map()->calculationSettings()).toString(Qt::ISODate)); + \
break; + }
+ case Value::fmt_Time: {
+ xmlWriter.addAttribute("office:value-type", "time");
+ xmlWriter.addAttribute("office:time-value",
+ value.asTime().toString("'PT'hh'H'mm'M'ss'S'"));
+ break;
+ }
+ case Value::fmt_String: {
+ xmlWriter.addAttribute("office:value-type", "string");
+ xmlWriter.addAttribute("office:string-value", value.asString());
+ break;
+ }
+ };
+}
+
+
+
+
} // Sheets
} // Calligra
diff --git a/sheets/odf/SheetsOdfSheet.cpp b/sheets/odf/SheetsOdfSheet.cpp
index f43758b..5254bf6 100644
--- a/sheets/odf/SheetsOdfSheet.cpp
+++ b/sheets/odf/SheetsOdfSheet.cpp
@@ -74,6 +74,8 @@
namespace Calligra {
namespace Sheets {
+class Cell;
+
template<typename T> class IntervalMap
{
public:
@@ -96,7 +98,11 @@ private:
namespace Odf {
+ // Sheet loading and saving
bool loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, \
OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash<QString, \
Conditions>& conditionalStyles); + bool saveSheet(Sheet *sheet, OdfSavingContext& \
tableContext); +
+ // Sheet loading - helper functions
/**
* Inserts the styles contained in \p styleRegions into the style storage.
* Looks automatic styles up in the map of preloaded automatic styles,
@@ -140,7 +146,7 @@ namespace Odf {
QString getPart(const KoXmlNode & part);
void replaceMacro(QString & text, const QString & old, const QString & newS);
- bool saveSheet(Sheet *sheet, OdfSavingContext& tableContext);
+ // Sheet saving - helper functions
QString saveSheetStyleName(Sheet *sheet, KoGenStyles &mainStyles);
void saveColRowCell(Sheet *sheet, int maxCols, int maxRows, OdfSavingContext& \
tableContext);
void saveCells(Sheet *sheet, int row, int maxCols, OdfSavingContext& \
tableContext); @@ -150,8 +156,15 @@ namespace Odf {
void convertPart(Sheet *sheet, const QString & part, KoXmlWriter & xmlWriter);
bool compareRows(Sheet *sheet, int row1, int row2, int maxCols, \
OdfSavingContext& tableContext);
+ // sheet settings
void loadSheetSettings(Sheet *sheet, const KoOasisSettings::NamedMap &settings);
void saveSheetSettings(Sheet *sheet, KoXmlWriter &settingsWriter);
+
+ // Cell loading - in SheetsOdfCell
+ bool loadCell(Cell *cell, const KoXmlElement& element, OdfLoadingContext& \
tableContext, + const Styles& autoStyles, const QString& cellStyleName,
+ QList<ShapeLoadingData>& shapeData);
+ bool saveCell(Cell *cell, int &repeated, OdfSavingContext& tableContext);
}
// *************** Loading *****************
@@ -980,8 +993,7 @@ int Odf::loadRowFormat(Sheet *sheet, const KoXmlElement& row, int \
&rowIndex, cellStyleName = columnStyles.get(columnIndex);
Cell cell(sheet, columnIndex, rowIndex);
-#warning TODO new style odf
- cell.loadOdf(cellElement, tableContext, autoStyles, cellStyleName, \
shapeData); + loadCell(&cell, cellElement, tableContext, autoStyles, \
cellStyleName, shapeData);
if (!cell.comment().isEmpty())
sheet->cellStorage()->setComment(Region(columnIndex, rowIndex, \
numberColumns, number, sheet), cell.comment()); @@ -1287,7 +1299,7 @@ void \
Odf::saveColRowCell(Sheet *sheet, int maxCols, int maxRows, OdfSavingContex if \
(repeated > 1)
xmlWriter.addAttribute("table:number-rows-repeated", repeated);
if (!style.isDefault()) {
- KoGenStyle currentDefaultCellStyle; // the type is determined in \
saveOdfCellStyle + KoGenStyle currentDefaultCellStyle; // the type is \
determined in saveCellStyle #warning TODO new style odf
const QString name = style.saveOdf(currentDefaultCellStyle, \
mainStyles,
sheet->map()->styleManager());
@@ -1318,7 +1330,7 @@ void Odf::saveColRowCell(Sheet *sheet, int maxCols, int \
maxRows, OdfSavingContex i = j - 1; /*it's already incremented in the for loop*/
} else { // row is not empty
if (!style.isDefault()) {
- KoGenStyle currentDefaultCellStyle; // the type is determined in \
saveOdfCellStyle + KoGenStyle currentDefaultCellStyle; // the type is \
determined in saveCellStyle #warning TODO new style odf
const QString name = style.saveOdf(currentDefaultCellStyle, \
mainStyles,
sheet->map()->styleManager());
@@ -1389,8 +1401,7 @@ void Odf::saveCells(Sheet *sheet, int row, int maxCols, \
OdfSavingContext& tableC
int repeated = 1;
int column = i;
-#warning TODO new style odf
- cell.saveOdf(row, column, repeated, tableContext);
+ saveCell(&cell, repeated, tableContext);
i += repeated;
// stop if we reached the end column
if (i > maxCols || nextCell.isNull())
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic