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