1 // xlsxsharedstrings.cpp
2 
3 #include <QtGlobal>
4 #include <QXmlStreamWriter>
5 #include <QXmlStreamReader>
6 #include <QDir>
7 #include <QFile>
8 #include <QDebug>
9 #include <QBuffer>
10 
11 #include "xlsxrichstring.h"
12 #include "xlsxsharedstrings_p.h"
13 #include "xlsxutility_p.h"
14 #include "xlsxformat_p.h"
15 #include "xlsxcolor_p.h"
16 
17 QT_BEGIN_NAMESPACE_XLSX
18 
19 /*
20  * Note that, when we open an existing .xlsx file (broken file?),
21  * duplicated string items may exist in the shared string table.
22  *
23  * In such case, the size of stringList will larger than stringTable.
24  * Duplicated items can be removed once we loaded all the worksheets.
25  */
26 
SharedStrings(CreateFlag flag)27 SharedStrings::SharedStrings(CreateFlag flag)
28     :AbstractOOXmlFile(flag)
29 {
30     m_stringCount = 0;
31 }
32 
count() const33 int SharedStrings::count() const
34 {
35     return m_stringCount;
36 }
37 
isEmpty() const38 bool SharedStrings::isEmpty() const
39 {
40     return m_stringList.isEmpty();
41 }
42 
addSharedString(const QString & string)43 int SharedStrings::addSharedString(const QString &string)
44 {
45     return addSharedString(RichString(string));
46 }
47 
addSharedString(const RichString & string)48 int SharedStrings::addSharedString(const RichString &string)
49 {
50     m_stringCount += 1;
51 
52     auto it = m_stringTable.find(string);
53     if (it != m_stringTable.end()) {
54         it->count += 1;
55         return it->index;
56     }
57 
58     int index = m_stringList.size();
59     m_stringTable[string] = XlsxSharedStringInfo(index);
60     m_stringList.append(string);
61     return index;
62 }
63 
incRefByStringIndex(int idx)64 void SharedStrings::incRefByStringIndex(int idx)
65 {
66     if (idx <0 || idx >= m_stringList.size()) {
67         qDebug("SharedStrings: invlid index");
68         return;
69     }
70 
71     addSharedString(m_stringList[idx]);
72 }
73 
74 /*
75  * Broken, don't use.
76  */
removeSharedString(const QString & string)77 void SharedStrings::removeSharedString(const QString &string)
78 {
79     removeSharedString(RichString(string));
80 }
81 
82 /*
83  * Broken, don't use.
84  */
removeSharedString(const RichString & string)85 void SharedStrings::removeSharedString(const RichString &string)
86 {
87     auto it = m_stringTable.find(string);
88     if (it == m_stringTable.end())
89         return;
90 
91     m_stringCount -= 1;
92 
93     it->count -= 1;
94 
95     if (it->count <= 0) {
96         for (int i=it->index+1; i<m_stringList.size(); ++i)
97             m_stringTable[m_stringList[i]].index -= 1;
98 
99         m_stringList.removeAt(it->index);
100         m_stringTable.remove(string);
101     }
102 }
103 
getSharedStringIndex(const QString & string) const104 int SharedStrings::getSharedStringIndex(const QString &string) const
105 {
106     return getSharedStringIndex(RichString(string));
107 }
108 
getSharedStringIndex(const RichString & string) const109 int SharedStrings::getSharedStringIndex(const RichString &string) const
110 {
111     auto it = m_stringTable.constFind(string);
112     if (it != m_stringTable.constEnd())
113         return it->index;
114     return -1;
115 }
116 
getSharedString(int index) const117 RichString SharedStrings::getSharedString(int index) const
118 {
119     if (index < m_stringList.count() && index >= 0)
120         return m_stringList[index];
121     return RichString();
122 }
123 
getSharedStrings() const124 QList<RichString> SharedStrings::getSharedStrings() const
125 {
126     return m_stringList;
127 }
128 
writeRichStringPart_rPr(QXmlStreamWriter & writer,const Format & format) const129 void SharedStrings::writeRichStringPart_rPr(QXmlStreamWriter &writer, const Format &format) const
130 {
131     if (!format.hasFontData())
132         return;
133 
134     if (format.fontBold())
135         writer.writeEmptyElement(QStringLiteral("b"));
136     if (format.fontItalic())
137         writer.writeEmptyElement(QStringLiteral("i"));
138     if (format.fontStrikeOut())
139         writer.writeEmptyElement(QStringLiteral("strike"));
140     if (format.fontOutline())
141         writer.writeEmptyElement(QStringLiteral("outline"));
142     if (format.boolProperty(FormatPrivate::P_Font_Shadow))
143         writer.writeEmptyElement(QStringLiteral("shadow"));
144     if (format.hasProperty(FormatPrivate::P_Font_Underline)) {
145         Format::FontUnderline u = format.fontUnderline();
146         if (u != Format::FontUnderlineNone) {
147             writer.writeEmptyElement(QStringLiteral("u"));
148             if (u== Format::FontUnderlineDouble)
149                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("double"));
150             else if (u == Format::FontUnderlineSingleAccounting)
151                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("singleAccounting"));
152             else if (u == Format::FontUnderlineDoubleAccounting)
153                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("doubleAccounting"));
154         }
155     }
156     if (format.hasProperty(FormatPrivate::P_Font_Script)) {
157         Format::FontScript s = format.fontScript();
158         if (s != Format::FontScriptNormal) {
159             writer.writeEmptyElement(QStringLiteral("vertAlign"));
160             if (s == Format::FontScriptSuper)
161                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("superscript"));
162             else
163                 writer.writeAttribute(QStringLiteral("val"), QStringLiteral("subscript"));
164         }
165     }
166 
167     if (format.hasProperty(FormatPrivate::P_Font_Size)) {
168         writer.writeEmptyElement(QStringLiteral("sz"));
169         writer.writeAttribute(QStringLiteral("val"), QString::number(format.fontSize()));
170     }
171 
172     if (format.hasProperty(FormatPrivate::P_Font_Color)) {
173         XlsxColor color = format.property(FormatPrivate::P_Font_Color).value<XlsxColor>();
174         color.saveToXml(writer);
175     }
176 
177     if (!format.fontName().isEmpty()) {
178         writer.writeEmptyElement(QStringLiteral("rFont"));
179         writer.writeAttribute(QStringLiteral("val"), format.fontName());
180     }
181     if (format.hasProperty(FormatPrivate::P_Font_Family)) {
182         writer.writeEmptyElement(QStringLiteral("family"));
183         writer.writeAttribute(QStringLiteral("val"), QString::number(format.intProperty(FormatPrivate::P_Font_Family)));
184     }
185 
186     if (format.hasProperty(FormatPrivate::P_Font_Scheme)) {
187         writer.writeEmptyElement(QStringLiteral("scheme"));
188         writer.writeAttribute(QStringLiteral("val"), format.stringProperty(FormatPrivate::P_Font_Scheme));
189     }
190 }
191 
saveToXmlFile(QIODevice * device) const192 void SharedStrings::saveToXmlFile(QIODevice *device) const
193 {
194     QXmlStreamWriter writer(device);
195 
196     if (m_stringList.size() != m_stringTable.size()) {
197         //Duplicated string items exist in m_stringList
198         //Clean up can not be done here, as the indices
199         //have been used when we save the worksheets part.
200     }
201 
202     writer.writeStartDocument(QStringLiteral("1.0"), true);
203     writer.writeStartElement(QStringLiteral("sst"));
204     writer.writeAttribute(QStringLiteral("xmlns"), QStringLiteral("http://schemas.openxmlformats.org/spreadsheetml/2006/main"));
205     writer.writeAttribute(QStringLiteral("count"), QString::number(m_stringCount));
206     writer.writeAttribute(QStringLiteral("uniqueCount"), QString::number(m_stringList.size()));
207 
208     for (const RichString &string : m_stringList) {
209         writer.writeStartElement(QStringLiteral("si"));
210         if (string.isRichString()) {
211             //Rich text string
212             for (int i=0; i<string.fragmentCount(); ++i) {
213                 writer.writeStartElement(QStringLiteral("r"));
214                 if (string.fragmentFormat(i).hasFontData()) {
215                     writer.writeStartElement(QStringLiteral("rPr"));
216                     writeRichStringPart_rPr(writer, string.fragmentFormat(i));
217                     writer.writeEndElement();// rPr
218                 }
219                 writer.writeStartElement(QStringLiteral("t"));
220                 if (isSpaceReserveNeeded(string.fragmentText(i)))
221                     writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
222                 writer.writeCharacters(string.fragmentText(i));
223                 writer.writeEndElement();// t
224 
225                 writer.writeEndElement(); //r
226             }
227         } else {
228             writer.writeStartElement(QStringLiteral("t"));
229             QString pString = string.toPlainString();
230             if (isSpaceReserveNeeded(pString))
231                 writer.writeAttribute(QStringLiteral("xml:space"), QStringLiteral("preserve"));
232             writer.writeCharacters(pString);
233             writer.writeEndElement();//t
234         }
235         writer.writeEndElement();//si
236     }
237 
238     writer.writeEndElement(); //sst
239     writer.writeEndDocument();
240 }
241 
readString(QXmlStreamReader & reader)242 void SharedStrings::readString(QXmlStreamReader &reader)
243 {
244     Q_ASSERT(reader.name() == QLatin1String("si"));
245 
246     RichString richString;
247 
248     while (!reader.atEnd() && !(reader.name() == QLatin1String("si") && reader.tokenType() == QXmlStreamReader::EndElement)) {
249         reader.readNextStartElement();
250         if (reader.tokenType() == QXmlStreamReader::StartElement) {
251             if (reader.name() == QLatin1String("r"))
252                 readRichStringPart(reader, richString);
253             else if (reader.name() == QLatin1String("t"))
254                 readPlainStringPart(reader, richString);
255         }
256     }
257 
258     int idx = m_stringList.size();
259     m_stringTable[richString] = XlsxSharedStringInfo(idx, 0);
260     m_stringList.append(richString);
261 }
262 
readRichStringPart(QXmlStreamReader & reader,RichString & richString)263 void SharedStrings::readRichStringPart(QXmlStreamReader &reader, RichString &richString)
264 {
265     Q_ASSERT(reader.name() == QLatin1String("r"));
266 
267     QString text;
268     Format format;
269     while (!reader.atEnd() && !(reader.name() == QLatin1String("r") && reader.tokenType() == QXmlStreamReader::EndElement)) {
270         reader.readNextStartElement();
271         if (reader.tokenType() == QXmlStreamReader::StartElement) {
272             if (reader.name() == QLatin1String("rPr")) {
273                 format = readRichStringPart_rPr(reader);
274             } else if (reader.name() == QLatin1String("t")) {
275                 text = reader.readElementText();
276             }
277         }
278     }
279     richString.addFragment(text, format);
280 }
281 
readPlainStringPart(QXmlStreamReader & reader,RichString & richString)282 void SharedStrings::readPlainStringPart(QXmlStreamReader &reader, RichString &richString)
283 {
284     Q_ASSERT(reader.name() == QLatin1String("t"));
285 
286     //QXmlStreamAttributes attributes = reader.attributes();
287 
288 	// NOTICE: CHECK POINT
289     QString text = reader.readElementText();
290     richString.addFragment(text, Format());
291 }
292 
readRichStringPart_rPr(QXmlStreamReader & reader)293 Format SharedStrings::readRichStringPart_rPr(QXmlStreamReader &reader)
294 {
295     Q_ASSERT(reader.name() == QLatin1String("rPr"));
296     Format format;
297     while (!reader.atEnd() && !(reader.name() == QLatin1String("rPr") && reader.tokenType() == QXmlStreamReader::EndElement)) {
298         reader.readNextStartElement();
299         if (reader.tokenType() == QXmlStreamReader::StartElement) {
300             QXmlStreamAttributes attributes = reader.attributes();
301             if (reader.name() == QLatin1String("rFont")) {
302                 format.setFontName(attributes.value(QLatin1String("val")).toString());
303             } else if (reader.name() == QLatin1String("charset")) {
304                 format.setProperty(FormatPrivate::P_Font_Charset, attributes.value(QLatin1String("val")).toString().toInt());
305             } else if (reader.name() == QLatin1String("family")) {
306                 format.setProperty(FormatPrivate::P_Font_Family, attributes.value(QLatin1String("val")).toString().toInt());
307             } else if (reader.name() == QLatin1String("b")) {
308                 format.setFontBold(true);
309             } else if (reader.name() == QLatin1String("i")) {
310                 format.setFontItalic(true);
311             } else if (reader.name() == QLatin1String("strike")) {
312                 format.setFontStrikeOut(true);
313             } else if (reader.name() == QLatin1String("outline")) {
314                 format.setFontOutline(true);
315             } else if (reader.name() == QLatin1String("shadow")) {
316                 format.setProperty(FormatPrivate::P_Font_Shadow, true);
317             } else if (reader.name() == QLatin1String("condense")) {
318                 format.setProperty(FormatPrivate::P_Font_Condense, attributes.value(QLatin1String("val")).toString().toInt());
319             } else if (reader.name() == QLatin1String("extend")) {
320                 format.setProperty(FormatPrivate::P_Font_Extend, attributes.value(QLatin1String("val")).toString().toInt());
321             } else if (reader.name() == QLatin1String("color")) {
322                 XlsxColor color;
323                 color.loadFromXml(reader);
324                 format.setProperty(FormatPrivate::P_Font_Color, color);
325             } else if (reader.name() == QLatin1String("sz")) {
326                 format.setFontSize(attributes.value(QLatin1String("val")).toString().toInt());
327             } else if (reader.name() == QLatin1String("u")) {
328                 QString value = attributes.value(QLatin1String("val")).toString();
329                 if (value == QLatin1String("double"))
330                     format.setFontUnderline(Format::FontUnderlineDouble);
331                 else if (value == QLatin1String("doubleAccounting"))
332                     format.setFontUnderline(Format::FontUnderlineDoubleAccounting);
333                 else if (value == QLatin1String("singleAccounting"))
334                     format.setFontUnderline(Format::FontUnderlineSingleAccounting);
335                 else
336                     format.setFontUnderline(Format::FontUnderlineSingle);
337             } else if (reader.name() == QLatin1String("vertAlign")) {
338                 QString value = attributes.value(QLatin1String("val")).toString();
339                 if (value == QLatin1String("superscript"))
340                     format.setFontScript(Format::FontScriptSuper);
341                 else if (value == QLatin1String("subscript"))
342                     format.setFontScript(Format::FontScriptSub);
343             } else if (reader.name() == QLatin1String("scheme")) {
344                 format.setProperty(FormatPrivate::P_Font_Scheme, attributes.value(QLatin1String("val")).toString());
345             }
346         }
347     }
348     return format;
349 }
350 
loadFromXmlFile(QIODevice * device)351 bool SharedStrings::loadFromXmlFile(QIODevice *device)
352 {
353     QXmlStreamReader reader(device);
354     int count = 0;
355     bool hasUniqueCountAttr=true;
356     while (!reader.atEnd()) {
357          QXmlStreamReader::TokenType token = reader.readNext();
358          if (token == QXmlStreamReader::StartElement) {
359              if (reader.name() == QLatin1String("sst")) {
360                  QXmlStreamAttributes attributes = reader.attributes();
361                  if ((hasUniqueCountAttr = attributes.hasAttribute(QLatin1String("uniqueCount"))))
362                      count = attributes.value(QLatin1String("uniqueCount")).toString().toInt();
363              } else if (reader.name() == QLatin1String("si")) {
364                  readString(reader);
365              }
366          }
367     }
368 
369     if (hasUniqueCountAttr && m_stringList.size() != count) {
370         qDebug("Error: Shared string count");
371         return false;
372     }
373 
374     if (m_stringList.size() != m_stringTable.size()) {
375         //qDebug("Warning: Duplicated items exist in shared string table.");
376         //Nothing we can do here, as indices of the strings will be used when loading sheets.
377     }
378 
379     return true;
380 }
381 
382 QT_END_NAMESPACE_XLSX
383