andre@1: /****************************************************************************
andre@1: ** Copyright (c) 2013-2014 Debao Zhang <hello@debao.me>
andre@1: ** All right reserved.
andre@1: **
andre@1: ** Permission is hereby granted, free of charge, to any person obtaining
andre@1: ** a copy of this software and associated documentation files (the
andre@1: ** "Software"), to deal in the Software without restriction, including
andre@1: ** without limitation the rights to use, copy, modify, merge, publish,
andre@1: ** distribute, sublicense, and/or sell copies of the Software, and to
andre@1: ** permit persons to whom the Software is furnished to do so, subject to
andre@1: ** the following conditions:
andre@1: **
andre@1: ** The above copyright notice and this permission notice shall be
andre@1: ** included in all copies or substantial portions of the Software.
andre@1: **
andre@1: ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
andre@1: ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
andre@1: ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
andre@1: ** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
andre@1: ** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
andre@1: ** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
andre@1: ** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
andre@1: **
andre@1: ****************************************************************************/
andre@1: #include "xlsxrichstring.h"
andre@1: #include "xlsxsharedstrings_p.h"
andre@1: #include "xlsxutility_p.h"
andre@1: #include "xlsxformat_p.h"
andre@1: #include "xlsxcolor_p.h"
andre@1: #include <QXmlStreamWriter>
andre@1: #include <QXmlStreamReader>
andre@1: #include <QDir>
andre@1: #include <QFile>
andre@1: #include <QDebug>
andre@1: #include <QBuffer>
andre@1: 
andre@1: namespace QXlsx {
andre@1: 
andre@1: /*
andre@1:  * Note that, when we open an existing .xlsx file (broken file?),
andre@1:  * duplicated string items may exist in the shared string table.
andre@1:  *
andre@1:  * In such case, the size of stringList will larger than stringTable.
andre@1:  * Duplicated items can be removed once we loaded all the worksheets.
andre@1:  */
andre@1: 
andre@1: SharedStrings::SharedStrings(CreateFlag flag)
andre@1:     :AbstractOOXmlFile(flag)
andre@1: {
andre@1:     m_stringCount = 0;
andre@1: }
andre@1: 
andre@1: int SharedStrings::count() const
andre@1: {
andre@1:     return m_stringCount;
andre@1: }
andre@1: 
andre@1: bool SharedStrings::isEmpty() const
andre@1: {
andre@1:     return m_stringList.isEmpty();
andre@1: }
andre@1: 
andre@1: int SharedStrings::addSharedString(const QString &string)
andre@1: {
andre@1:     return addSharedString(RichString(string));
andre@1: }
andre@1: 
andre@1: int SharedStrings::addSharedString(const RichString &string)
andre@1: {
andre@1:     m_stringCount += 1;
andre@1: 
andre@1:     if (m_stringTable.contains(string)) {
andre@1:         XlsxSharedStringInfo &item = m_stringTable[string];
andre@1:         item.count += 1;
andre@1:         return item.index;
andre@1:     }
andre@1: 
andre@1:     int index = m_stringList.size();
andre@1:     m_stringTable[string] = XlsxSharedStringInfo(index);
andre@1:     m_stringList.append(string);
andre@1:     return index;
andre@1: }
andre@1: 
andre@1: void SharedStrings::incRefByStringIndex(int idx)
andre@1: {
andre@1:     if (idx <0 || idx >= m_stringList.size()) {
andre@1:         qDebug("SharedStrings: invlid index");
andre@1:         return;
andre@1:     }
andre@1: 
andre@1:     addSharedString(m_stringList[idx]);
andre@1: }
andre@1: 
andre@1: /*
andre@1:  * Broken, don't use.
andre@1:  */
andre@1: void SharedStrings::removeSharedString(const QString &string)
andre@1: {
andre@1:     removeSharedString(RichString(string));
andre@1: }
andre@1: 
andre@1: /*
andre@1:  * Broken, don't use.
andre@1:  */
andre@1: void SharedStrings::removeSharedString(const RichString &string)
andre@1: {
andre@1:     if (!m_stringTable.contains(string))
andre@1:         return;
andre@1: 
andre@1:     m_stringCount -= 1;
andre@1: 
andre@1:     XlsxSharedStringInfo &item = m_stringTable[string];
andre@1:     item.count -= 1;
andre@1: 
andre@1:     if (item.count <= 0) {
andre@1:         for (int i=item.index+1; i<m_stringList.size(); ++i)
andre@1:             m_stringTable[m_stringList[i]].index -= 1;
andre@1: 
andre@1:         m_stringList.removeAt(item.index);
andre@1:         m_stringTable.remove(string);
andre@1:     }
andre@1: }
andre@1: 
andre@1: int SharedStrings::getSharedStringIndex(const QString &string) const
andre@1: {
andre@1:     return getSharedStringIndex(RichString(string));
andre@1: }
andre@1: 
andre@1: int SharedStrings::getSharedStringIndex(const RichString &string) const
andre@1: {
andre@1:     if (m_stringTable.contains(string))
andre@1:         return m_stringTable[string].index;
andre@1:     return -1;
andre@1: }
andre@1: 
andre@1: RichString SharedStrings::getSharedString(int index) const
andre@1: {
andre@1:     if (index < m_stringList.count() && index >= 0)
andre@1:         return m_stringList[index];
andre@1:     return RichString();
andre@1: }
andre@1: 
andre@1: QList<RichString> SharedStrings::getSharedStrings() const
andre@1: {
andre@1:     return m_stringList;
andre@1: }
andre@1: 
andre@1: void SharedStrings::writeRichStringPart_rPr(QXmlStreamWriter &writer, const Format &format) const
andre@1: {
andre@1:     if (!format.hasFontData())
andre@1:         return;
andre@1: 
andre@1:     if (format.fontBold())
andre@1:         writer.writeEmptyElement(QStringLiteral("b"));
andre@1:     if (format.fontItalic())
andre@1:         writer.writeEmptyElement(QStringLiteral("i"));
andre@1:     if (format.fontStrikeOut())
andre@1:         writer.writeEmptyElement(QStringLiteral("strike"));
andre@1:     if (format.fontOutline())
andre@1:         writer.writeEmptyElement(QStringLiteral("outline"));
andre@1:     if (format.boolProperty(FormatPrivate::P_Font_Shadow))
andre@1:         writer.writeEmptyElement(QStringLiteral("shadow"));
andre@1:     if (format.hasProperty(FormatPrivate::P_Font_Underline)) {
andre@1:         Format::FontUnderline u = format.fontUnderline();
andre@1:         if (u != Format::FontUnderlineNone) {
andre@1:             writer.writeEmptyElement(QStringLiteral("u"));
andre@1:             if (u== Format::FontUnderlineDouble)
andre@1:                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("double"));
andre@1:             else if (u == Format::FontUnderlineSingleAccounting)
andre@1:                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("singleAccounting"));
andre@1:             else if (u == Format::FontUnderlineDoubleAccounting)
andre@1:                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("doubleAccounting"));
andre@1:         }
andre@1:     }
andre@1:     if (format.hasProperty(FormatPrivate::P_Font_Script)) {
andre@1:         Format::FontScript s = format.fontScript();
andre@1:         if (s != Format::FontScriptNormal) {
andre@1:             writer.writeEmptyElement(QStringLiteral("vertAlign"));
andre@1:             if (s == Format::FontScriptSuper)
andre@1:                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("superscript"));
andre@1:             else
andre@1:                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("subscript"));
andre@1:         }
andre@1:     }
andre@1: 
andre@1:     if (format.hasProperty(FormatPrivate::P_Font_Size)) {
andre@1:         writer.writeEmptyElement(QStringLiteral("sz"));
andre@1:         writer.writeAttribute(QStringLiteral("val"), QString::number(format.fontSize()));
andre@1:     }
andre@1: 
andre@1:     if (format.hasProperty(FormatPrivate::P_Font_Color)) {
andre@1:         XlsxColor color = format.property(FormatPrivate::P_Font_Color).value<XlsxColor>();
andre@1:         color.saveToXml(writer);
andre@1:     }
andre@1: 
andre@1:     if (!format.fontName().isEmpty()) {
andre@1:         writer.writeEmptyElement(QStringLiteral("rFont"));
andre@1:         writer.writeAttribute(QStringLiteral("val"), format.fontName());
andre@1:     }
andre@1:     if (format.hasProperty(FormatPrivate::P_Font_Family)) {
andre@1:         writer.writeEmptyElement(QStringLiteral("family"));
andre@1:         writer.writeAttribute(QStringLiteral("val"), QString::number(format.intProperty(FormatPrivate::P_Font_Family)));
andre@1:     }
andre@1: 
andre@1:     if (format.hasProperty(FormatPrivate::P_Font_Scheme)) {
andre@1:         writer.writeEmptyElement(QStringLiteral("scheme"));
andre@1:         writer.writeAttribute(QStringLiteral("val"), format.stringProperty(FormatPrivate::P_Font_Scheme));
andre@1:     }
andre@1: }
andre@1: 
andre@1: void SharedStrings::saveToXmlFile(QIODevice *device) const
andre@1: {
andre@1:     QXmlStreamWriter writer(device);
andre@1: 
andre@1:     if (m_stringList.size() != m_stringTable.size()) {
andre@1:         //Duplicated string items exist in m_stringList
andre@1:         //Clean up can not be done here, as the indices
andre@1:         //have been used when we save the worksheets part.
andre@1:     }
andre@1: 
andre@1:     writer.writeStartDocument(QStringLiteral("1.0"), true);
andre@1:     writer.writeStartElement(QStringLiteral("sst"));
andre@1:     writer.writeAttribute(QStringLiteral("xmlns"), QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main"));
andre@1:     writer.writeAttribute(QStringLiteral("count"), QString::number(m_stringCount));
andre@1:     writer.writeAttribute(QStringLiteral("uniqueCount"), QString::number(m_stringList.size()));
andre@1: 
andre@1:     foreach (RichString string, m_stringList) {
andre@1:         writer.writeStartElement(QStringLiteral("si"));
andre@1:         if (string.isRichString()) {
andre@1:             //Rich text string
andre@1:             for (int i=0; i<string.fragmentCount(); ++i) {
andre@1:                 writer.writeStartElement(QStringLiteral("r"));
andre@1:                 if (string.fragmentFormat(i).hasFontData()) {
andre@1:                     writer.writeStartElement(QStringLiteral("rPr"));
andre@1:                     writeRichStringPart_rPr(writer, string.fragmentFormat(i));
andre@1:                     writer.writeEndElement();// rPr
andre@1:                 }
andre@1:                 writer.writeStartElement(QStringLiteral("t"));
andre@1:                 if (isSpaceReserveNeeded(string.fragmentText(i)))
andre@1:                     writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
andre@1:                 writer.writeCharacters(string.fragmentText(i));
andre@1:                 writer.writeEndElement();// t
andre@1: 
andre@1:                 writer.writeEndElement(); //r
andre@1:             }
andre@1:         } else {
andre@1:             writer.writeStartElement(QStringLiteral("t"));
andre@1:             QString pString = string.toPlainString();
andre@1:             if (isSpaceReserveNeeded(pString))
andre@1:                 writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
andre@1:             writer.writeCharacters(pString);
andre@1:             writer.writeEndElement();//t
andre@1:         }
andre@1:         writer.writeEndElement();//si
andre@1:     }
andre@1: 
andre@1:     writer.writeEndElement(); //sst
andre@1:     writer.writeEndDocument();
andre@1: }
andre@1: 
andre@1: void SharedStrings::readString(QXmlStreamReader &reader)
andre@1: {
andre@1:     Q_ASSERT(reader.name() == QLatin1String("si"));
andre@1: 
andre@1:     RichString richString;
andre@1: 
andre@1:     while (!reader.atEnd() && !(reader.name() == QLatin1String("si") && reader.tokenType() == QXmlStreamReader::EndElement)) {
andre@1:         reader.readNextStartElement();
andre@1:         if (reader.tokenType() == QXmlStreamReader::StartElement) {
andre@1:             if (reader.name() == QLatin1String("r"))
andre@1:                 readRichStringPart(reader, richString);
andre@1:             else if (reader.name() == QLatin1String("t"))
andre@1:                 readPlainStringPart(reader, richString);
andre@1:         }
andre@1:     }
andre@1: 
andre@1:     int idx = m_stringList.size();
andre@1:     m_stringTable[richString] = XlsxSharedStringInfo(idx, 0);
andre@1:     m_stringList.append(richString);
andre@1: }
andre@1: 
andre@1: void SharedStrings::readRichStringPart(QXmlStreamReader &reader, RichString &richString)
andre@1: {
andre@1:     Q_ASSERT(reader.name() == QLatin1String("r"));
andre@1: 
andre@1:     QString text;
andre@1:     Format format;
andre@1:     while (!reader.atEnd() && !(reader.name() == QLatin1String("r") && reader.tokenType() == QXmlStreamReader::EndElement)) {
andre@1:         reader.readNextStartElement();
andre@1:         if (reader.tokenType() == QXmlStreamReader::StartElement) {
andre@1:             if (reader.name() == QLatin1String("rPr")) {
andre@1:                 format = readRichStringPart_rPr(reader);
andre@1:             } else if (reader.name() == QLatin1String("t")) {
andre@1:                 text = reader.readElementText();
andre@1:             }
andre@1:         }
andre@1:     }
andre@1:     richString.addFragment(text, format);
andre@1: }
andre@1: 
andre@1: void SharedStrings::readPlainStringPart(QXmlStreamReader &reader, RichString &richString)
andre@1: {
andre@1:     Q_ASSERT(reader.name() == QLatin1String("t"));
andre@1: 
andre@1:     //QXmlStreamAttributes attributes = reader.attributes();
andre@1: 
andre@1:     QString text = reader.readElementText();
andre@1:     richString.addFragment(text, Format());
andre@1: }
andre@1: 
andre@1: Format SharedStrings::readRichStringPart_rPr(QXmlStreamReader &reader)
andre@1: {
andre@1:     Q_ASSERT(reader.name() == QLatin1String("rPr"));
andre@1:     Format format;
andre@1:     while (!reader.atEnd() && !(reader.name() == QLatin1String("rPr") && reader.tokenType() == QXmlStreamReader::EndElement)) {
andre@1:         reader.readNextStartElement();
andre@1:         if (reader.tokenType() == QXmlStreamReader::StartElement) {
andre@1:             QXmlStreamAttributes attributes = reader.attributes();
andre@1:             if (reader.name() == QLatin1String("rFont")) {
andre@1:                 format.setFontName(attributes.value(QLatin1String("val")).toString());
andre@1:             } else if (reader.name() == QLatin1String("charset")) {
andre@1:                 format.setProperty(FormatPrivate::P_Font_Charset, attributes.value(QLatin1String("val")).toString().toInt());
andre@1:             } else if (reader.name() == QLatin1String("family")) {
andre@1:                 format.setProperty(FormatPrivate::P_Font_Family, attributes.value(QLatin1String("val")).toString().toInt());
andre@1:             } else if (reader.name() == QLatin1String("b")) {
andre@1:                 format.setFontBold(true);
andre@1:             } else if (reader.name() == QLatin1String("i")) {
andre@1:                 format.setFontItalic(true);
andre@1:             } else if (reader.name() == QLatin1String("strike")) {
andre@1:                 format.setFontStrikeOut(true);
andre@1:             } else if (reader.name() == QLatin1String("outline")) {
andre@1:                 format.setFontOutline(true);
andre@1:             } else if (reader.name() == QLatin1String("shadow")) {
andre@1:                 format.setProperty(FormatPrivate::P_Font_Shadow, true);
andre@1:             } else if (reader.name() == QLatin1String("condense")) {
andre@1:                 format.setProperty(FormatPrivate::P_Font_Condense, attributes.value(QLatin1String("val")).toString().toInt());
andre@1:             } else if (reader.name() == QLatin1String("extend")) {
andre@1:                 format.setProperty(FormatPrivate::P_Font_Extend, attributes.value(QLatin1String("val")).toString().toInt());
andre@1:             } else if (reader.name() == QLatin1String("color")) {
andre@1:                 XlsxColor color;
andre@1:                 color.loadFromXml(reader);
andre@1:                 format.setProperty(FormatPrivate::P_Font_Color, color);
andre@1:             } else if (reader.name() == QLatin1String("sz")) {
andre@1:                 format.setFontSize(attributes.value(QLatin1String("val")).toString().toInt());
andre@1:             } else if (reader.name() == QLatin1String("u")) {
andre@1:                 QString value = attributes.value(QLatin1String("val")).toString();
andre@1:                 if (value == QLatin1String("double"))
andre@1:                     format.setFontUnderline(Format::FontUnderlineDouble);
andre@1:                 else if (value == QLatin1String("doubleAccounting"))
andre@1:                     format.setFontUnderline(Format::FontUnderlineDoubleAccounting);
andre@1:                 else if (value == QLatin1String("singleAccounting"))
andre@1:                     format.setFontUnderline(Format::FontUnderlineSingleAccounting);
andre@1:                 else
andre@1:                     format.setFontUnderline(Format::FontUnderlineSingle);
andre@1:             } else if (reader.name() == QLatin1String("vertAlign")) {
andre@1:                 QString value = attributes.value(QLatin1String("val")).toString();
andre@1:                 if (value == QLatin1String("superscript"))
andre@1:                     format.setFontScript(Format::FontScriptSuper);
andre@1:                 else if (value == QLatin1String("subscript"))
andre@1:                     format.setFontScript(Format::FontScriptSub);
andre@1:             } else if (reader.name() == QLatin1String("scheme")) {
andre@1:                 format.setProperty(FormatPrivate::P_Font_Scheme, attributes.value(QLatin1String("val")).toString());
andre@1:             }
andre@1:         }
andre@1:     }
andre@1:     return format;
andre@1: }
andre@1: 
andre@1: bool SharedStrings::loadFromXmlFile(QIODevice *device)
andre@1: {
andre@1:     QXmlStreamReader reader(device);
andre@1:     int count = 0;
andre@1:     bool hasUniqueCountAttr=true;
andre@1:     while (!reader.atEnd()) {
andre@1:          QXmlStreamReader::TokenType token = reader.readNext();
andre@1:          if (token == QXmlStreamReader::StartElement) {
andre@1:              if (reader.name() == QLatin1String("sst")) {
andre@1:                  QXmlStreamAttributes attributes = reader.attributes();
andre@1:                  if ((hasUniqueCountAttr = attributes.hasAttribute(QLatin1String("uniqueCount"))))
andre@1:                      count = attributes.value(QLatin1String("uniqueCount")).toString().toInt();
andre@1:              } else if (reader.name() == QLatin1String("si")) {
andre@1:                  readString(reader);
andre@1:              }
andre@1:          }
andre@1:     }
andre@1: 
andre@1:     if (hasUniqueCountAttr && m_stringList.size() != count) {
andre@1:         qDebug("Error: Shared string count");
andre@1:         return false;
andre@1:     }
andre@1: 
andre@1:     if (m_stringList.size() != m_stringTable.size()) {
andre@1:         //qDebug("Warning: Duplicated items exist in shared string table.");
andre@1:         //Nothing we can do here, as indices of the strings will be used when loading sheets.
andre@1:     }
andre@1: 
andre@1:     return true;
andre@1: }
andre@1: 
andre@1: } //namespace