Fix Issue 7: Cell string misplacement when rich text exist

A private class RichString has been added. More work is needed
This commit is contained in:
Debao Zhang
2013-11-18 11:52:10 +08:00
parent d0cb3e6301
commit 00350d4251
16 changed files with 496 additions and 41 deletions
+4 -2
View File
@@ -31,7 +31,8 @@ HEADERS += $$PWD/xlsxdocpropscore_p.h \
$$PWD/xlsxcell_p.h \
$$PWD/xlsxdatavalidation.h \
$$PWD/xlsxdatavalidation_p.h \
$$PWD/xlsxcellrange.h
$$PWD/xlsxcellrange.h \
$$PWD/xlsxrichstring_p.h
SOURCES += $$PWD/xlsxdocpropscore.cpp \
$$PWD/xlsxdocpropsapp.cpp \
@@ -53,4 +54,5 @@ SOURCES += $$PWD/xlsxdocpropscore.cpp \
$$PWD/xlsxdocument.cpp \
$$PWD/xlsxcell.cpp \
$$PWD/xlsxdatavalidation.cpp \
$$PWD/xlsxcellrange.cpp
$$PWD/xlsxcellrange.cpp \
$$PWD/xlsxrichstring.cpp
+12
View File
@@ -121,4 +121,16 @@ QDateTime Cell::dateTime() const
return datetimeFromNumber(d->value.toDouble(), d->parent->workbook()->isDate1904());
}
/*!
* Returns whether the cell is probably a rich string or not
*/
bool Cell::isRichString() const
{
Q_D(const Cell);
if (d->dataType != String && d->dataType != InlineString)
return false;
return d->richString.isRichString();
}
QT_END_NAMESPACE_XLSX
+2
View File
@@ -58,6 +58,8 @@ public:
bool isDateTime() const;
QDateTime dateTime() const;
bool isRichString() const;
~Cell();
private:
friend class Worksheet;
+5
View File
@@ -28,6 +28,9 @@
#include "xlsxglobal.h"
#include "xlsxcell.h"
#include "xlsxcellrange.h"
#include "xlsxrichstring_p.h"
#include <QList>
#include <QSharedPointer>
QT_BEGIN_NAMESPACE_XLSX
@@ -43,6 +46,8 @@ public:
Format *format;
CellRange range; //used for arrayFormula
RichString richString;
Worksheet *parent;
Cell *q_ptr;
};
+2
View File
@@ -38,6 +38,7 @@ QT_BEGIN_NAMESPACE_XLSX
class Styles;
class Worksheet;
class WorksheetPrivate;
class RichString;
class FormatPrivate;
class Q_XLSX_EXPORT Format
@@ -214,6 +215,7 @@ private:
friend class Styles;
friend class Worksheet;
friend class WorksheetPrivate;
friend class RichString;
friend class ::FormatTest;
Format();
+1 -2
View File
@@ -160,8 +160,7 @@ bool Package::parsePackage(QIODevice *packageDevice)
//In normal case this should be sharedStrings.xml which in xl
QString name = rels_sharedStrings[0].target;
QString path = xlworkbook_Dir + QLatin1String("/") + name;
QSharedPointer<SharedStrings> sst= SharedStrings::loadFromXmlData(zipReader.fileData(path));
m_document->workbook()->d_ptr->sharedStrings = sst;
m_document->workbook()->d_ptr->sharedStrings->loadFromXmlData(zipReader.fileData(path));
}
//load theme
+173
View File
@@ -0,0 +1,173 @@
/****************************************************************************
** Copyright (c) 2013 Debao Zhang <hello@debao.me>
** All right reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#include "xlsxrichstring_p.h"
QT_BEGIN_NAMESPACE_XLSX
RichString::RichString()
:m_dirty(true)
{
}
RichString::RichString(const QString text)
:m_dirty(true)
{
addFragment(text, 0);
}
bool RichString::isRichString() const
{
if (fragmentCount() > 1) //Is this enough??
return true;
return false;
}
bool RichString::isEmtpy() const
{
return m_fragmentTexts.size() == 0;
}
QString RichString::toPlainString() const
{
if (isEmtpy())
return QString();
if (m_fragmentTexts.size() == 1)
return m_fragmentTexts[0];
return m_fragmentTexts.join(QString());
}
int RichString::fragmentCount() const
{
return m_fragmentTexts.size();
}
void RichString::addFragment(const QString &text, Format *format)
{
m_fragmentTexts.append(text);
m_fragmentFormats.append(format);
m_dirty = true;
}
QString RichString::fragmentText(int index) const
{
if (index < 0 || index >= fragmentCount())
return QString();
return m_fragmentTexts[index];
}
Format *RichString::fragmentFormat(int index) const
{
if (index < 0 || index >= fragmentCount())
return 0;
return m_fragmentFormats[index];
}
/*!
* \internal
*/
QByteArray RichString::idKey() const
{
if (m_dirty) {
RichString *rs = const_cast<RichString *>(this);
QByteArray bytes;
if (!isRichString()) {
bytes = toPlainString().toUtf8();
} else {
//Generate a hash value base on QByteArray ?
bytes.append("@@QtXlsxRichString=");
for (int i=0; i<fragmentCount(); ++i) {
bytes.append("@Text");
bytes.append(m_fragmentTexts[i].toUtf8());
bytes.append("@Format");
if (m_fragmentFormats[i])
bytes.append(m_fragmentFormats[i]->fontKey());
}
}
rs->m_idKey = bytes;
rs->m_dirty = false;
}
return m_idKey;
}
bool operator==(const RichString &rs1, const RichString &rs2)
{
if (rs1.fragmentCount() != rs2.fragmentCount())
return false;
return rs1.idKey() == rs2.idKey();
}
bool operator!=(const RichString &rs1, const RichString &rs2)
{
if (rs1.fragmentCount() != rs2.fragmentCount())
return true;
return rs1.idKey() != rs2.idKey();
}
/*!
* \internal
*/
bool operator<(const RichString &rs1, const RichString &rs2)
{
return rs1.idKey() < rs2.idKey();
}
bool operator ==(const RichString &rs1, const QString &rs2)
{
if (rs1.fragmentCount() == 1 && rs1.fragmentText(0) == rs2) //format == 0
return true;
return false;
}
bool operator !=(const RichString &rs1, const QString &rs2)
{
if (rs1.fragmentCount() == 1 && rs1.fragmentText(0) == rs2) //format == 0
return false;
return true;
}
bool operator ==(const QString &rs1, const RichString &rs2)
{
return rs2 == rs1;
}
bool operator !=(const QString &rs1, const RichString &rs2)
{
return rs2 != rs1;
}
uint qHash(const RichString &rs, uint seed) Q_DECL_NOTHROW
{
return qHash(rs.idKey(), seed);
}
QT_END_NAMESPACE_XLSX
+79
View File
@@ -0,0 +1,79 @@
/****************************************************************************
** Copyright (c) 2013 Debao Zhang <hello@debao.me>
** All right reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#ifndef XLSXRICHSTRING_P_H
#define XLSXRICHSTRING_P_H
#include "xlsxglobal.h"
#include "xlsxformat.h"
#include <QStringList>
QT_BEGIN_NAMESPACE_XLSX
class RichString;
// qHash is a friend, but we can't use default arguments for friends (§8.3.6.4)
XLSX_AUTOTEST_EXPORT uint qHash(const RichString &rs, uint seed = 0) Q_DECL_NOTHROW;
class XLSX_AUTOTEST_EXPORT RichString
{
public:
RichString();
explicit RichString(const QString text);
bool isRichString() const;
bool isEmtpy() const;
QString toPlainString() const;
int fragmentCount() const;
void addFragment(const QString &text, Format *format);
QString fragmentText(int index) const;
Format *fragmentFormat(int index) const;
private:
friend XLSX_AUTOTEST_EXPORT uint qHash(const RichString &rs, uint seed) Q_DECL_NOTHROW;
friend XLSX_AUTOTEST_EXPORT bool operator==(const RichString &rs1, const RichString &rs2);
friend XLSX_AUTOTEST_EXPORT bool operator!=(const RichString &rs1, const RichString &rs2);
friend XLSX_AUTOTEST_EXPORT bool operator<(const RichString &rs1, const RichString &rs2);
QByteArray idKey() const;
QStringList m_fragmentTexts;
QList<Format *> m_fragmentFormats;
QByteArray m_idKey;
bool m_dirty;
};
XLSX_AUTOTEST_EXPORT bool operator==(const RichString &rs1, const RichString &rs2);
XLSX_AUTOTEST_EXPORT bool operator!=(const RichString &rs1, const RichString &rs2);
XLSX_AUTOTEST_EXPORT bool operator<(const RichString &rs1, const RichString &rs2);
XLSX_AUTOTEST_EXPORT bool operator==(const RichString &rs1, const QString &rs2);
XLSX_AUTOTEST_EXPORT bool operator==(const QString &rs1, const RichString &rs2);
XLSX_AUTOTEST_EXPORT bool operator!=(const RichString &rs1, const QString &rs2);
XLSX_AUTOTEST_EXPORT bool operator!=(const QString &rs1, const RichString &rs2);
QT_END_NAMESPACE_XLSX
Q_DECLARE_METATYPE(QXlsx::RichString);
#endif // XLSXRICHSTRING_P_H
+89 -18
View File
@@ -22,6 +22,7 @@
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#include "xlsxrichstring_p.h"
#include "xlsxsharedstrings_p.h"
#include "xlsxxmlwriter_p.h"
#include "xlsxxmlreader_p.h"
@@ -44,6 +45,11 @@ int SharedStrings::count() const
}
int SharedStrings::addSharedString(const QString &string)
{
return addSharedString(RichString(string));
}
int SharedStrings::addSharedString(const RichString &string)
{
m_stringCount += 1;
@@ -70,6 +76,11 @@ void SharedStrings::incRefByStringIndex(int idx)
}
void SharedStrings::removeSharedString(const QString &string)
{
removeSharedString(RichString(string));
}
void SharedStrings::removeSharedString(const RichString &string)
{
if (!m_stringTable.contains(string))
return;
@@ -89,20 +100,25 @@ void SharedStrings::removeSharedString(const QString &string)
}
int SharedStrings::getSharedStringIndex(const QString &string) const
{
return getSharedStringIndex(RichString(string));
}
int SharedStrings::getSharedStringIndex(const RichString &string) const
{
if (m_stringTable.contains(string))
return m_stringTable[string].index;
return -1;
}
QString SharedStrings::getSharedString(int index) const
RichString SharedStrings::getSharedString(int index) const
{
if (index < m_stringList.count() && index >= 0)
return m_stringList[index];
return QString();
return RichString();
}
QStringList SharedStrings::getSharedStrings() const
QList<RichString> SharedStrings::getSharedStrings() const
{
return m_stringList;
}
@@ -117,16 +133,29 @@ void SharedStrings::saveToXmlFile(QIODevice *device) const
writer.writeAttribute(QStringLiteral("count"), QString::number(m_stringCount));
writer.writeAttribute(QStringLiteral("uniqueCount"), QString::number(m_stringTable.size()));
foreach (QString string, m_stringList) {
foreach (RichString string, m_stringList) {
writer.writeStartElement(QStringLiteral("si"));
if (string.contains(QRegularExpression(QStringLiteral("^<r>"))) || string.contains(QRegularExpression(QStringLiteral("</r>$")))) {
//Rich text string,
// writer.writeCharacters(string);
if (string.isRichString()) {
//Rich text string
for (int i=0; i<string.fragmentCount(); ++i) {
if (string.fragmentFormat(i)) {
writer.writeStartElement(QStringLiteral("rPr"));
//:Todo
writer.writeEndElement();// rPr
}
writer.writeStartElement(QStringLiteral("t"));
writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
writer.writeCharacters(string.fragmentText(i));
writer.writeEndElement();// t
}
} else {
writer.writeStartElement(QStringLiteral("t"));
if (string.contains(QRegularExpression(QStringLiteral("^\\s"))) || string.contains(QRegularExpression(QStringLiteral("\\s$"))))
QString pString = string.toPlainString();
if (pString.contains(QRegularExpression(QStringLiteral("^\\s")))
|| pString.contains(QRegularExpression(QStringLiteral("\\s$")))) {
writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
writer.writeCharacters(string);
}
writer.writeCharacters(pString);
writer.writeEndElement();//t
}
writer.writeEndElement();//si
@@ -146,6 +175,56 @@ QByteArray SharedStrings::saveToXmlData() const
return data;
}
void SharedStrings::readString(XmlStreamReader &reader)
{
Q_ASSERT(reader.name() == QLatin1String("si"));
RichString richString;
while (!(reader.name() == QLatin1String("si") && reader.tokenType() == QXmlStreamReader::EndElement)) {
reader.readNextStartElement();
if (reader.tokenType() == QXmlStreamReader::StartElement) {
if (reader.name() == QLatin1String("r"))
readRichStringPart(reader, richString);
else if (reader.name() == QLatin1String("t"))
readPlainStringPart(reader, richString);
}
}
int idx = m_stringList.size();
m_stringTable[richString] = XlsxSharedStringInfo(idx, 0);
m_stringList.append(richString);
}
void SharedStrings::readRichStringPart(XmlStreamReader &reader, RichString &richString)
{
Q_ASSERT(reader.name() == QLatin1String("r"));
QString text;
Format *format=0;
while (!(reader.name() == QLatin1String("r") && reader.tokenType() == QXmlStreamReader::EndElement)) {
reader.readNextStartElement();
if (reader.tokenType() == QXmlStreamReader::StartElement) {
if (reader.name() == QLatin1String("rPr")) {
//:Todo
} else if (reader.name() == QLatin1String("t")) {
text = reader.readElementText();
}
}
}
richString.addFragment(text, format);
}
void SharedStrings::readPlainStringPart(XmlStreamReader &reader, RichString &richString)
{
Q_ASSERT(reader.name() == QLatin1String("t"));
//QXmlStreamAttributes attributes = reader.attributes();
QString text = reader.readElementText();
richString.addFragment(text, 0);
}
bool SharedStrings::loadFromXmlFile(QIODevice *device)
{
XmlStreamReader reader(device);
@@ -157,15 +236,7 @@ bool SharedStrings::loadFromXmlFile(QIODevice *device)
QXmlStreamAttributes attributes = reader.attributes();
count = attributes.value(QLatin1String("uniqueCount")).toString().toInt();
} else if (reader.name() == QLatin1String("si")) {
if (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("t")) {
// QXmlStreamAttributes attributes = reader.attributes();
QString string = reader.readElementText();
int idx = m_stringList.size();
m_stringTable[string] = XlsxSharedStringInfo(idx, 0);
m_stringList.append(string);
}
}
readString(reader);
}
}
}
+15 -4
View File
@@ -26,6 +26,7 @@
#define XLSXSHAREDSTRINGS_H
#include "xlsxglobal.h"
#include "xlsxrichstring_p.h"
#include <QHash>
#include <QStringList>
#include <QSharedPointer>
@@ -34,6 +35,9 @@ class QIODevice;
namespace QXlsx {
class XmlStreamReader;
class RichString;
class XlsxSharedStringInfo
{
public:
@@ -53,12 +57,15 @@ public:
int count() const;
int addSharedString(const QString &string);
int addSharedString(const RichString &string);
void removeSharedString(const QString &string);
void removeSharedString(const RichString &string);
void incRefByStringIndex(int idx);
int getSharedStringIndex(const QString &string) const;
QString getSharedString(int index) const;
QStringList getSharedStrings() const;
int getSharedStringIndex(const RichString &string) const;
RichString getSharedString(int index) const;
QList<RichString> getSharedStrings() const;
void saveToXmlFile(QIODevice *device) const;
QByteArray saveToXmlData() const;
@@ -66,8 +73,12 @@ public:
bool loadFromXmlData(const QByteArray &data);
private:
QHash<QString, XlsxSharedStringInfo> m_stringTable; //for fast lookup
QStringList m_stringList;
void readString(XmlStreamReader &reader); // <si>
void readRichStringPart(XmlStreamReader &reader, RichString &rich); // <r>
void readPlainStringPart(XmlStreamReader &reader, RichString &rich); // <v>
QHash<RichString, XlsxSharedStringInfo> m_stringTable; //for fast lookup
QList<RichString> m_stringList;
int m_stringCount;
};
+21 -3
View File
@@ -22,6 +22,7 @@
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#include "xlsxrichstring_p.h"
#include "xlsxworksheet.h"
#include "xlsxworksheet_p.h"
#include "xlsxworkbook.h"
@@ -1163,7 +1164,21 @@ void WorksheetPrivate::writeCellData(XmlStreamWriter &writer, int row, int col,
} else if (cell->dataType() == Cell::InlineString) {
writer.writeAttribute(QStringLiteral("t"), QStringLiteral("inlineStr"));
writer.writeStartElement(QStringLiteral("is"));
writer.writeTextElement(QStringLiteral("t"), cell->value().toString());
if (cell->isRichString()) {
//Rich text string
RichString string = cell->d_ptr->richString;
for (int i=0; i<string.fragmentCount(); ++i) {
writer.writeStartElement(QStringLiteral("rPr"));
//:Todo
writer.writeEndElement();// rPr
writer.writeStartElement(QStringLiteral("t"));
writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
writer.writeCharacters(string.fragmentText(i));
writer.writeEndElement();// t
}
} else {
writer.writeTextElement(QStringLiteral("t"), cell->value().toString());
}
writer.writeEndElement();//is
} else if (cell->dataType() == Cell::Numeric){
double value = cell->value().toDouble();
@@ -1874,8 +1889,10 @@ void WorksheetPrivate::readSheetData(XmlStreamReader &reader)
if (reader.name() == QLatin1String("v")) {
int sst_idx = reader.readElementText().toInt();
sharedStrings()->incRefByStringIndex(sst_idx);
QString value = sharedStrings()->getSharedString(sst_idx);
QSharedPointer<Cell> data(new Cell(value ,Cell::String, format, q));
RichString rs = sharedStrings()->getSharedString(sst_idx);
QSharedPointer<Cell> data(new Cell(rs.toPlainString() ,Cell::String, format, q));
if (rs.isRichString())
data->d_ptr->richString = rs;
cellTable[pos.x()][pos.y()] = QSharedPointer<Cell>(data);
}
}
@@ -1884,6 +1901,7 @@ void WorksheetPrivate::readSheetData(XmlStreamReader &reader)
while (!(reader.name() == QLatin1String("c") && reader.tokenType() == QXmlStreamReader::EndElement)) {
reader.readNextStartElement();
if (reader.tokenType() == QXmlStreamReader::StartElement) {
//:Todo, add rich text read support
if (reader.name() == QLatin1String("t")) {
QString value = reader.readElementText();
QSharedPointer<Cell> data(new Cell(value, Cell::InlineString, format, q));