[KLF Backend][KLF Tools][KLF Home]
KLatexFormula Project
klfdatautil.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * file klfdatautil.h
3 * This file is part of the KLatexFormula Project.
4 * Copyright (C) 2011 by Philippe Faist
5 * philippe.faist at bluewin.ch
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21 ***************************************************************************/
22/* $Id$ */
23
24#include <qglobal.h>
25#include <QObject>
26#include <QByteArray>
27#include <QString>
28#include <QUrl>
29#include <QTextCodec>
30#include <QDateTime>
31#include <QRect>
32#include <QIcon>
33#include <QColor>
34#include <QBrush>
35#include <QDomDocument>
36#include <QTextFormat>
37
38#include "klfdefs.h"
39#include "klfpobj.h"
40#include "klfutil.h"
41#include "klfdatautil.h"
42
43#include "klfdatautil_p.h"
44
45
46static inline bool klf_is_hex_char(char c)
47{
48 return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
49}
50
51
52
53#define KLF_BRUSH_STYLE(sty) \
54 { Qt::sty##Pattern, #sty }
55
56static struct { int brushStyle; const char *style; } klf_brush_styles[] = {
57 { Qt::NoBrush, "NoBrush" },
58 { Qt::SolidPattern, "" },
59 { Qt::SolidPattern, "Solid" },
60 KLF_BRUSH_STYLE(Dense1),
61 KLF_BRUSH_STYLE(Dense2),
62 KLF_BRUSH_STYLE(Dense3),
63 KLF_BRUSH_STYLE(Dense4),
64 KLF_BRUSH_STYLE(Dense5),
65 KLF_BRUSH_STYLE(Dense6),
66 KLF_BRUSH_STYLE(Dense7),
67 KLF_BRUSH_STYLE(Hor),
68 KLF_BRUSH_STYLE(Ver),
69 KLF_BRUSH_STYLE(Cross),
70 KLF_BRUSH_STYLE(BDiag),
71 KLF_BRUSH_STYLE(FDiag),
72 KLF_BRUSH_STYLE(DiagCross),
73 { -1, NULL }
74};
75
76
77
78#define KLF_TEXT_FORMAT_FORMAT(fmt) \
79 { QTextFormat::fmt##Format, #fmt "Format" }
80
81static struct { int formatId; const char *format; } klf_text_format_formats[] = {
89 { -100, NULL }
90};
91
92
93#define KLF_TEXT_FORMAT_PROP(p, type) \
94 { QTextFormat::p, #p, #type }
95
96static struct { int propId; const char *key; const char *type; } klf_text_format_props[] = {
97 KLF_TEXT_FORMAT_PROP(ForegroundBrush, QBrush),
98 KLF_TEXT_FORMAT_PROP(BackgroundBrush, QBrush),
99 KLF_TEXT_FORMAT_PROP(FontFamily, QString),
100 KLF_TEXT_FORMAT_PROP(FontPointSize, int),
101 KLF_TEXT_FORMAT_PROP(FontWeight, int),
102 KLF_TEXT_FORMAT_PROP(FontItalic, bool),
103 KLF_TEXT_FORMAT_PROP(TextUnderlineStyle, int),
104 // add more keys for short-hands
105 { QTextFormat::ForegroundBrush, "FG", "QBrush" },
106 { QTextFormat::BackgroundBrush, "BG", "QBrush" },
107
108 { -1, NULL, NULL }
109};
110
111static struct { const char * keyword; int propId; QVariant fixed_value; } klf_text_format_keywords[] = {
112 { "NORMALWEIGHT", QTextFormat::FontWeight, QVariant(QFont::Normal) },
113 { "BOLD", QTextFormat::FontWeight, QVariant(QFont::Bold) },
114 { "NORMALSTYLE", QTextFormat::FontItalic, QVariant(false) },
115 { "ITALIC", QTextFormat::FontItalic, QVariant(true) },
116
117 { NULL, -1, QVariant() }
118};
119
120
121
122
123KLF_EXPORT QByteArray klfDataToEscaped(const QByteArray& value_ba, char escapechar)
124{
126
127 klfDbg("len="<<value_ba.size()<<" , data=`"<<value_ba<<"' escapechar="<<klfFmtCC("'\\x%02X'", (int)escapechar));
128
129 QByteArray data;
130 int k;
131 for (k = 0; k < value_ba.size(); ++k) {
132 // qDebug("\tdata[%d] = %x = %c", k, (uchar)value_ba[k], value_ba[k]);
133 if (value_ba[k] >= 32 && value_ba[k] <= 126 && value_ba[k] != escapechar) {
134 // ascii-ok values, not backslash
135 data += value_ba[k];
136 } else if (value_ba[k] == escapechar) {
137 // double the escape char
138 data += escapechar;
139 data += escapechar;
140 } else {
141 data += escapechar;
142 data += QString("x%1").arg((uint)(uchar)value_ba[k], 2, 16, QChar('0')).toLatin1();
143 }
144 }
145 return data;
146}
147
149{
151 klfDbg("data=`"<<data<<"', escapechar="<<klfFmtCC("'\\x%02X'", (int)escapechar));
152
153 bool convertOk;
154 int k;
155 QByteArray value_ba;
156 k = 0;
157 while (k < data.size()) {
158 if (data[k] != escapechar) {
159 value_ba += data[k];
160 ++k;
161 continue;
162 }
163 // we have an escapechar
164 if (data[k] == escapechar && k+1 >= data.size()) {
165 value_ba += escapechar; // backslash at end of data
166 ++k;
167 continue;
168 }
169 // not at end of data
170 if (data[k+1] != 'x') {
171 // backslash followed by something else than 'x', so see if it is a standard escape sequence (e.g. '\n'),
172 // or add that escaped 'something else'
173 if (data[k+1] == 'n')
174 value_ba += '\n';
175 if (data[k+1] == '0')
176 value_ba += '\0';
177 if (data[k+1] == 't')
178 value_ba += '\t';
179 if (data[k+1] == 'a')
180 value_ba += '\a';
181 if (data[k+1] == 'b')
182 value_ba += '\b';
183 if (data[k+1] == 'f')
184 value_ba += '\f';
185 if (data[k+1] == 'r')
186 value_ba += '\r';
187 if (data[k+1] == 'v')
188 value_ba += '\v';
189 else
190 value_ba += data[k+1];
191 k += 2; // had to skip the backslash
192 continue;
193 }
194 // pos k points on '\\', pos k+1 points on 'x'
195 if (k+3 >= data.size() || !klf_is_hex_char(data[k+2]) || !klf_is_hex_char(data[k+3])) {
196 // ignore invalid escape sequence
197 klfDbg("ignoring invalid escape sequence `"<<data.mid(k,4)<<"'") ;
198 value_ba += data[k];
199 ++k;
200 continue;
201 }
202 // decode this char
203 uchar cval = data.mid(k+2, 2).toUInt(&convertOk, 16);
204 value_ba += (char)cval;
205 k += 4; // advance of backslash + 'x' + 2 digits
206 }
207 return value_ba;
208}
209
210
211static QByteArray encaps_list(const QList<QByteArray>& list)
212{
213 QByteArray data = "[";
214 for (int k = 0; k < list.size(); ++k) {
215 QByteArray d = list[k];
216 d.replace("\\", "\\\\");
217 d.replace(";", "\\;");
218 d.replace("[", "\\[");
219 d.replace("]", "\\]");
220 data += d;
221 if (k < list.size()-1)
222 data += ";";
223 }
224 data += "]";
225 return data;
226}
227
228// if 'ignore_empty_values' is TRUE, then the '=' sign is omitted with the value in a section is empty.
229static QByteArray encaps_map(const QList<QPair<QByteArray,QByteArray> >& sections, bool ignore_empty_values = false)
230{
231 QByteArray data;
232 data = "{";
233 bool first_item = true;
234 int k;
235 for (k = 0; k < sections.size(); ++k) {
236 if (!first_item) {
237 data += ";";
238 }
239 first_item = false;
240 QByteArray key = sections[k].first;
241 QByteArray val = sections[k].second;
242 // prepare the pair key=value
243 key.replace("\\", "\\\\");
244 key.replace(";", "\\;");
245 key.replace("=", "\\=");
246 val.replace("\\", "\\\\");
247 val.replace(";", "\\;");
248 if (val.isEmpty() && ignore_empty_values)
249 data += key;
250 else
251 data += key + "=" + val;
252 }
253 data += "}";
254 return data;
255}
256
257
258static QList<QByteArray> decaps_list(const QByteArray& ba_data)
259{
260 klfDbg("decaps_list, data="<<ba_data);
261 QByteArray data = ba_data.trimmed();
262 if (data[0] != '[')
263 return QList<QByteArray>();
264
265 QList<QByteArray> sections;
266 QByteArray chunk;
267 // first, split data. take into account escaped chars.
268 // k=1 to skip '['
269 int k = 1;
270 while (k < data.size()) {
271 if (data[k] == ';') { // element separator
272 // flush chunk as a new section
273 sections.append(chunk);
274 // and start a new section
275 chunk = QByteArray();
276 ++k;
277 }
278 if (data[k] == '\\') {
279 if (k+1 < data.size()) { // there exists a next char
280 chunk += data[k+1];
281 k += 2;
282 } else {
283 chunk += data[k];
284 ++k;
285 }
286 continue;
287 }
288 if (data[k] == ']') {
289 // end of list marker.
290 // flush last chunk into sections, and break.
291 if (!chunk.isEmpty())
292 sections.append(chunk);
293 chunk = "";
294 break;
295 }
296 // regular char, populate current chunk.
297 chunk += data[k];
298 ++k;
299 }
300 if (!chunk.isEmpty()) {
301 // missing ']' at end, tolerate this by adding the unfinished chunk to sections
302 sections.append(chunk);
303 }
304
305 klfDbg("sections="<<sections);
306
307 return sections;
308}
309
310static QList<QPair<QByteArray,QByteArray> > decaps_map(const QByteArray& ba_data, bool allow_empty_values = false)
311{
312 QByteArray data = ba_data.trimmed();
313 if (data[0] != '{')
315 if ( !data.contains('}') )
316 data += '}';
317
319 QByteArray chunkkey;
320 QByteArray chunkvalue;
321 QByteArray *curChunk = &chunkkey;
322 // first, split data. take into account escaped chars.
323 // k=1 to skip '{'
324 int k = 1;
325 while (k < data.size()) {
326 if (data[k] == ';') { // separator for next pair
327 // flush chunk as a new section
328 if (!allow_empty_values && curChunk == &chunkkey)
329 qWarning()<<KLF_FUNC_NAME<<": no '=' in pair at pos "<<k<<" in string: "<<data<<"";
330 sections << QPair<QByteArray,QByteArray>(chunkkey, chunkvalue);
331 // and start a new section
332 chunkkey = QByteArray();
333 chunkvalue = QByteArray();
334 curChunk = &chunkkey;
335 ++k;
336 }
337 if (data[k] == '\\') {
338 if (k+1 < data.size()) { // there exists a next char
339 *curChunk += data[k+1];
340 k += 2;
341 } else {
342 *curChunk += data[k];
343 ++k;
344 }
345 continue;
346 }
347 if (curChunk == &chunkkey && data[k] == '=') {
348 // currently reading key, switch to reading value
349 curChunk = &chunkvalue;
350 ++k;
351 continue;
352 }
353 if (data[k] == '}') {
354 // end of list marker.
355 // flush last chunk into sections, and break.
356 if (!allow_empty_values && curChunk == &chunkkey)
357 qWarning()<<"klfLoadVariantFromText: no '=' in pair at pos "<<k<<" in string: "<<data<<"";
358 sections << QPair<QByteArray,QByteArray>(chunkkey, chunkvalue);
359 break;
360 }
361 // regular char, populate current chunk.
362 *curChunk += data[k];
363 ++k;
364 }
365 return sections;
366}
367
368
369
370// returns root node. get the document with root.ownerDocument()
371static QDomElement make_xml_wrapper(const QString& rootname)
372{
373 QDomDocument xmldoc(rootname);
374 QDomElement root = xmldoc.createElement(rootname);
375 xmldoc.appendChild(root);
376 return root;
377}
378
379static QDomElement parse_xml_wrapper(const QByteArray& xmldata, const QString& shouldBeRootName)
380{
381 QDomDocument xmldoc(shouldBeRootName);
382 bool result = xmldoc.setContent(xmldata);
383 KLF_ASSERT_CONDITION(result, "Failed to read wrapper XML for klfLoadVariantFromText()",
384 return QDomElement() ) ;
385
386 QDomElement el = xmldoc.documentElement();
387 KLF_ASSERT_CONDITION( el.nodeName() == shouldBeRootName,
388 "Wrong XML root node in wrapper for klfLoadVariantFromText(): "
389 <<el.nodeName() , ; ) ;
390 return el;
391}
392
393KLF_EXPORT QByteArray klfSaveVariantToText(const QVariant& value, bool saveListAndMapsAsXML, QByteArray *savedType,
394 QByteArray *savedListOrMapType)
395{
397
398 QString s;
399 QByteArray data;
400 int k;
401
402 if (!value.isValid() || value.isNull()) {
403 klfDbg("saving null variant.");
404 if (savedType != NULL)
405 *savedType = QByteArray();
406 return QByteArray();
407 }
408
409 // values of value.type() are QMetaType::Type enum entries. See qt's doc.
410 switch ((int)value.type()) {
411 case QMetaType::Bool:
412 data = value.toBool() ? "true" : "false";
413 break;
414 case QMetaType::Int:
415 case QMetaType::UInt:
416 case QMetaType::Short:
417 case QMetaType::UShort:
418 case QMetaType::Long:
419 case QMetaType::ULong:
420 case QMetaType::LongLong:
421 case QMetaType::ULongLong:
422 case QMetaType::Double:
423 data = value.toString().toLocal8Bit();
424 break;
425 case QMetaType::Char:
426 {
427 char c = value.value<char>();
428 if (c >= 32 && c <= 126 && c != '\\') {
429 data = QByteArray(1, c);
430 } else if (c == '\\') {
431 data = "\\\\";
432 } else {
433 data = "\\" + QString::number(c, 16).toUpper().toLatin1();
434 }
435 break;
436 }
437 case QMetaType::QChar:
438 {
439 QChar c = value.toChar();
440 if (tc->canEncode(c) && c != '\\') {
441 data = tc->fromUnicode(QString(c));
442 } else if (c == '\\') {
443 data = "\\\\";
444 } else {
445 data = "\\" + QString::number(c.unicode(), 16).toUpper().toLatin1();
446 }
447 break;
448 }
449 case QMetaType::QString:
450 {
451 s = value.toString();
452 if (tc->canEncode(s)) {
453 // replace any `\' by `\\' (ie. escape backslashes)
454 data = tc->fromUnicode(s.replace("\\", "\\\\"));
455 } else {
456 // encode char by char, escaping as needed
457 data = QByteArray("");
458 for (k = 0; k < s.length(); ++k) {
459 if (tc->canEncode(s[k])) {
460 data += tc->fromUnicode(s.mid(k,1));
461 } else {
462 data += QString("\\x%1").arg((uint)s[k].unicode(), 4, 16, QChar('0')).toLatin1();
463 }
464 }
465 }
466 break;
467 }
468 case QMetaType::QStringList:
469 {
470 const QStringList list = value.toStringList();
471 QList<QByteArray> sections;
472 int k;
473 for (k = 0; k < list.size(); ++k) {
474 sections.append(klfDataToEscaped(list[k].toUtf8()));
475 }
476 data = encaps_list(sections);
477 break;
478 }
479 case QMetaType::QUrl:
480 data = value.toUrl().toEncoded(); break;
481 case QMetaType::QByteArray:
482 {
483 data = klfDataToEscaped(value.value<QByteArray>());
484 break;
485 }
486 case QMetaType::QDate:
487 data = value.value<QDate>().toString(Qt::SystemLocaleShortDate).toLocal8Bit(); break;
488 case QMetaType::QTime:
489 data = value.value<QTime>().toString(Qt::SystemLocaleShortDate).toLocal8Bit(); break;
490 case QMetaType::QDateTime:
491 data = value.value<QDateTime>().toString(Qt::SystemLocaleShortDate).toLocal8Bit(); break;
492 case QMetaType::QSize:
493 { QSize sz = value.toSize();
494 data = QString("(%1 %2)").arg(sz.width()).arg(sz.height()).toLatin1();
495 break;
496 }
497 case QMetaType::QPoint:
498 { QPoint pt = value.toPoint();
499 data = QString("(%1 %2)").arg(pt.x()).arg(pt.y()).toLatin1();
500 break;
501 }
502 case QMetaType::QRect:
503 { QRect r = value.toRect();
504 data = QString("(%1 %2 %3x%4)").arg(r.left()).arg(r.top()).arg(r.width()).arg(r.height()).toLatin1();
505 break;
506 }
507 case QMetaType::QColor:
508 { QColor c = value.value<QColor>();
509 klfDbg("Saving color "<<c<<": alpha="<<c.alpha()) ;
510 if (c.alpha() == 255)
511 data = QString("(%1 %2 %3)").arg(c.red()).arg(c.green()).arg(c.blue()).toLatin1();
512 else
513 data = QString("(%1 %2 %3 %4)").arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha()).toLatin1();
514 break;
515 }
516 case QMetaType::QFont:
517 { QFont f = value.value<QFont>();
518 data = "'" + f.family().toLocal8Bit() + "'";
519 switch (f.weight()) {
520 case QFont::Light: data += " Light"; break;
521 case QFont::Normal: break; //data += " Normal"; break;
522 case QFont::DemiBold: data += " DemiBold"; break;
523 case QFont::Bold: data += " Bold"; break;
524 case QFont::Black: data += " Black"; break;
525 default: data += QString(" Wgt=%1").arg(f.weight()); break;
526 }
527 switch (f.style()) {
528 case QFont::StyleNormal: break; //data += " Normal"; break;
529 case QFont::StyleItalic: data += " Italic"; break;
530 case QFont::StyleOblique: data += " Oblique"; break;
531 default: break;
532 }
533 // QFontInfo is preferred, if f was set with a pixelSize().
534 data += " " + QString::number(QFontInfo(f).pointSize()).toLatin1();
535 break;
536 }
537 case QMetaType::QBrush:
538 { QBrush b = value.value<QBrush>();
539 if (!b.matrix().isIdentity())
540 break; // forget about saving complex brushes here
541 int bstyle = b.style();
542 // find index in our brush style enum
543 int k;
544 bool found_style = false;
545 for (k = 0; klf_brush_styles[k].brushStyle >= 0 && klf_brush_styles[k].style != NULL; ++k) {
546 if (klf_brush_styles[k].brushStyle == bstyle) {
547 found_style = true;
548 break;
549 }
550 }
551 if (!found_style) {
552 // didn't find this style, this is a complex brush. Need to save it via a datastream.
553 break;
554 }
555 // found brush style. This is a simple brush with just a style and a color.
556 data = "(";
557 data += klf_brush_styles[k].style;
558 if (strlen(klf_brush_styles[k].style))
559 data += " ";
560 QColor c = b.color();
561 data += QString("%1 %2 %3 %4").arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
562 data += ")";
563 break;
564 }
565 case QMetaType::QTextFormat:
566 {
567 QTextFormat tf = value.value<QTextFormat>();
568 const QMap<int,QVariant> props = tf.properties();
569
571
572 // first find the QTextFormat type.
573 int k;
574 for (k = 0; klf_text_format_formats[k].format != NULL; ++k)
575 if (klf_text_format_formats[k].formatId == tf.type())
576 break;
577 if (klf_text_format_formats[k].format == NULL) {
578 // didn't find format, something is bound to go wrong, so fall back
579 // on Qt's datastream saving.
580 data = QByteArray();
581 break;
582 }
583 // found format. This will be the first (value-less) section.
584 sections << QPair<QByteArray,QByteArray>(klf_text_format_formats[k].format, QByteArray());
585
587 for (it = props.begin(); it != props.end(); ++it) {
588 int propId = it.key();
589 QVariant propValue = it.value();
590 // Add data for this property.
591
592 // first look to see if a keyword is already known to be available
593 for (k = 0; klf_text_format_keywords[k].keyword != NULL; ++k)
594 if (klf_text_format_keywords[k].propId == propId &&
595 klf_text_format_keywords[k].fixed_value == propValue)
596 break;
597 const char *kw = klf_text_format_keywords[k].keyword;
598 if (kw != NULL) {
599 // found a keyword for this property-value pair
600 QByteArray key = kw;
601 sections << QPair<QByteArray,QByteArray>(kw, QByteArray());
602 continue;
603 }
604
605 // now look to see if we can name the property
606 for (k = 0; klf_text_format_props[k].key != NULL; ++k)
607 if (klf_text_format_props[k].propId == propId)
608 break;
609 if (klf_text_format_props[k].key != NULL) {
610 // make sure the variant has the advertised type
611 if ( !strcmp(klf_text_format_props[k].type, propValue.typeName()) ) {
612 // found the property in our list of common properties
613 QByteArray key = klf_text_format_props[k].key;
614 QByteArray value = klfSaveVariantToText(propValue, true); // resort to XML for lists/maps...
615 sections << QPair<QByteArray,QByteArray>(key, value);
616 continue;
617 } else {
618 qWarning()<<KLF_FUNC_NAME<<": QTextFormat property "<<klf_text_format_props[k].key
619 <<" 's type is `"<<propValue.typeName()<<"' which is not the known type: "
620 <<klf_text_format_props[k].type;
621 }
622 }
623
624 // this property is unknown to us. store it as we can.
626 QByteArray value;
627 value = QByteArray("[")+propValue.typeName()+"]"+klfSaveVariantToText(propValue, true);
628 }
629 data = encaps_map(sections, true);
630 break;
631 }
632 case QMetaType::QVariantList:
633 {
634 klfDbg("Saving list!") ;
635 const QList<QVariant>& list = value.toList();
636 if (saveListAndMapsAsXML) {
637 QDomElement el = make_xml_wrapper("variant-list");
638 el = klfSaveVariantListToXML(list, el);
639 data = el.ownerDocument().toByteArray(-1);
640 } else {
641 QList<QByteArray> sections;
642 QByteArray innertype;
643 for (k = 0; k < list.size(); ++k) {
644 if (k == 0)
645 innertype = list[k].typeName();
646 if (innertype != list[k].typeName()) {
647 klfWarning("saving list: not all inner QVariants have same type. Found a "<<innertype
648 <<" along with a "<<list[k].typeName());
649 }
650 sections << klfSaveVariantToText(list[k]);
651 }
652 if (savedListOrMapType != NULL)
653 *savedListOrMapType = innertype;
654 data = encaps_list(sections);
655 }
656 break;
657 }
658 case QMetaType::QVariantMap:
659 {
660 klfDbg("Saving Map!") ;
661 const QMap<QString,QVariant>& map = value.toMap();
662 if (saveListAndMapsAsXML) {
663 QDomElement el = make_xml_wrapper("variant-map");
664 klfDbg("map="<<map) ;
665 el = klfSaveVariantMapToXML(map, el);
666 data = el.ownerDocument().toByteArray(-1);
667 klfDbg("saved XML: data="<<data) ;
668 } else {
670 QByteArray innertype, thistype;
671 bool firstround = true;
672 for (QMap<QString,QVariant>::const_iterator it = map.begin(); it != map.end(); ++it) {
674 QByteArray v = klfSaveVariantToText(it.value());
675 thistype = it.value().typeName();
676 if (firstround) {
677 innertype = thistype;
678 firstround = false;
679 }
680 if (innertype != thistype) {
681 klfWarning("saving map: not all inner QVariants have same type. Found a "<<innertype
682 <<" along with a "<<thistype);
683 }
684 sections << QPair<QByteArray,QByteArray>(k, v);
685 }
686 if (savedListOrMapType != NULL)
687 *savedListOrMapType = innertype;
688 data = encaps_map(sections);
689 }
690 break;
691 }
692 default:
693 break;
694 };
695
696 // -- some other types --
697
698 QByteArray typeName = value.typeName();
699
700 QByteArray typeSpec = QByteArray();
703 const_cast<KLFSpecifyableType*>(static_cast<const KLFSpecifyableType*>(value.data()));
704
705 typeSpec = t->specification();
706 if (savedType != NULL) {
707 *savedType = typeName + "/" + typeSpec;
708 }
709 } else {
710 if (savedType != NULL)
711 *savedType = typeName;
712 }
713
714 if (typeName == "KLFEnumType") {
715 // just save the integer value!
716 KLFEnumType e = value.value<KLFEnumType>();
717 data = QByteArray::number(e.value());
718 }
719
722 const_cast<KLFAbstractPropertizedObject*>(static_cast<const KLFAbstractPropertizedObject*>(value.data()));
723
724 bool hasfixedtypes = obj->hasFixedTypes();
725
726 QVariantMap props = obj->allProperties();
727 if (!hasfixedtypes) {
728 return klfSaveVariantToText(props, true); // save all with XML
729 }
730 // if we have fixed types, convert them all to text (this is human-readable)
731 QVariantMap propstexts;
732 for (QVariantMap::const_iterator it = props.begin(); it != props.end(); ++it) {
733 propstexts[it.key()] = klfSaveVariantToText(it.value(), true); // in case of list/map values, use XML
734 klfDbg("Saving property "<<it.key()<<" to text, value = "<<propstexts[it.key()]) ;
735 }
736 props = propstexts;
737 return klfSaveVariantToText(props, false); // save all with XML
738 // NOTE: WE HAVE USED 'return', not 'data = ', because this call to klfSaveVariantToText() is
739 // already "finalizing"
740 }
741
742 // protect data from some special sequences
743
744 if (data.startsWith("[QVariant]") || data.startsWith("\\")) { // protect this special sequence
745 data = "\\"+data;
746 }
747
748 // and provide a default encoding scheme in case no one up to now was able to
749 // format the data (this format is only machine-readable ...)
750
751 if (data.isNull()) {
752 QByteArray vdata;
753 {
754 QDataStream stream(&vdata, QIODevice::WriteOnly);
755 stream.setVersion(QDataStream::Qt_4_4);
756 stream << value;
757 }
758 QByteArray vdata_esc = klfDataToEscaped(vdata);
759 qDebug("\tVariant value is %s, len=%d", vdata.constData(), vdata.size());
760 data = QByteArray("[QVariant]");
761 data += vdata_esc;
762 }
763
764 klfDbg( "klfSaveVariantToText("<<value<<"): saved data (len="<<data.size()<<") : "<<data ) ;
765 return data;
766}
767
768
769
770
771KLF_EXPORT QVariant klfLoadVariantFromText(const QByteArray& stringdata, const char * dataTypeName,
772 const char *listOrMapDataTypeName)
773{
775
776 // SOME REGULAR EXPRESSIONS
777
778#define RX_INT "-?\\d+"
779#define RX_COORD_SEP "\\s*(?:[,;]|\\s)\\s*" // note: non-capturing parenthesis
780#define RX_SIZE_SEP "\\s*(?:[,;x]|\\s)\\s*" // note: non-capturing parenthesis
781
782 // 1 2
783 QRegExp v2rx("^\\(?\\s*(" RX_INT ")" RX_COORD_SEP "(" RX_INT ")\\s*\\)?");
784 static const int V2RX_X = 1, V2RX_Y = 2;
785
786 // 1 2
787 QRegExp szrx("^\\(?\\s*(" RX_INT ")" RX_SIZE_SEP "(" RX_INT ")\\s*\\)?");
788 static const int SZRX_W = 1, SZRX_H = 2;
789
790 // 1 2
791 QRegExp rectrx("^\\(?\\s*(" RX_INT ")" RX_COORD_SEP "(" RX_INT ")"
792 // 3
793 "(?:" RX_COORD_SEP "|\\s*([+])\\s*)"
794 //4 5 6
795 "(" RX_INT ")(?:" RX_COORD_SEP "|\\s*([x])\\s*)(" RX_INT ")\\s*\\)?");
796 static const int RECTRX_X1 = 1, RECTRX_Y1 = 2, RECTRX_MIDDLESEP_PLUS = 3,
797 RECTRX_X2orW = 4, RECTRX_LASTSEP_X = 5, RECTRX_Y2orH = 6;
798
799 // 1 2 3
800 QRegExp colrx("^(?:rgba?)?\\(?\\s*(\\d+)" RX_COORD_SEP "(\\d+)" RX_COORD_SEP "(\\d+)"
801 //4 5
802 "(" RX_COORD_SEP "(\\d+))?\\s*\\)?", Qt::CaseInsensitive);
803 static const int COLRX_R = 1, COLRX_G = 2, COLRX_B = 3, COLRX_MAYBE_ALPHA = 4, COLRX_A = 5;
804
805 // 1 2 3
806 QRegExp brushrx("^(?:q?brush)?\\(?\\s*(?:([A-Za-z_]\\w*)" RX_COORD_SEP ")?(\\d+)" RX_COORD_SEP "(\\d+)"
807 // 4 5 6
808 RX_COORD_SEP "(\\d+)" "(" RX_COORD_SEP "(\\d+))?" "\\s*\\)?", Qt::CaseInsensitive);
809 static const int BRUSHRX_STYLE = 1, BRUSHRX_R = 2, BRUSHRX_G = 3, BRUSHRX_B = 4, BRUSHRX_A = 6;
810
811 // 1 2
812 QRegExp fontrx("^([\"']?)\\s*(.+)\\s*\\1"
813 //3 4 5
814 "(\\s+(Light|Normal|DemiBold|Bold|Black|Wgt\\s*=\\s*(\\d+)))?"
815 //6 7 8 9
816 "(\\s+(Normal|Italic|Oblique))?(\\s+(\\d+))?$");
817 fontrx.setMinimal(true); // don't match Light|Normal|DemiBold|... etc as part of font name
818 static const int FONTRX_FAMILY = 2, FONTRX_WEIGHT_TEXT = 4, FONTRX_WEIGHT_VALUE = 5,
819 FONTRX_STYLE_TEXT = 7, FONTRX_POINTSIZE = 9;
820
821
822 // START DECODING TEXT
823
824 QByteArray data = stringdata; // might need slight modifications before parsing
825
826 // first check: if the type string is empty, we're loading a Null variant...
827 if (dataTypeName == NULL || *dataTypeName == 0) {
828 klfDbg("loading null variant.");
829 return QVariant();
830 }
831
832 QVariant value;
833 if (data.startsWith("[QVariant]")) {
834 QByteArray vdata_esc = data.mid(strlen("[QVariant]"));
835 QByteArray vdata = klfEscapedToData(vdata_esc);
836 klfDbg( "\tAbout to read raw variant from datastr="<<vdata_esc<<", ie. from data len="<<vdata.size() ) ;
837 QDataStream stream(vdata);
838 stream.setVersion(QDataStream::Qt_4_4);
839 stream >> value;
840 return value;
841 }
842 if (data.startsWith("\\"))
843 data = data.mid(1);
844
845 klfDbg( "Will start loading a `"<<dataTypeName<<"' from data (len="<<data.size()<<") : "<<data ) ;
846
847
848 QByteArray tname = dataTypeName;
849
850 int idslash;
851 QByteArray tspecification = QByteArray();
852 if ((idslash = tname.indexOf('/')) >= 0) {
853 tspecification = tname.mid(idslash+1); // extract the specification ...
854 tname = tname.left(idslash); // ... and truncate the type name at the slash.
855 klfDbg("tspecification="<<tspecification<<", tname="<<tname) ;
856 }
857
858 // now, start reading.
859 int type = QMetaType::type(tname);
860 klfDbg("Type is "<<type) ;
861 bool convertOk = false; // in case we break; somewhere, it's (by default) because of failed convertion.
862 int k;
863 switch (type) {
864 case QMetaType::Bool:
865 {
866 klfDbg("bool!") ;
867 QByteArray lowerdata = data.trimmed().toLower();
868 QChar c = QChar(lowerdata[0]);
869 // true, yes, on, 1
870 return QVariant::fromValue<bool>(c == 't' || c == 'y' || c == '1' || lowerdata == "on");
871 }
872 case QMetaType::Int:
873 {
874 klfDbg("int!") ;
875 int i = data.toInt(&convertOk);
876 if (convertOk)
877 return QVariant::fromValue<int>(i);
878 break;
879 }
880 case QMetaType::UInt:
881 {
882 klfDbg("uint!") ;
883 uint i = data.toUInt(&convertOk);
884 if (convertOk)
885 return QVariant::fromValue<uint>(i);
886 break;
887 }
888 case QMetaType::Short:
889 {
890 klfDbg("short!") ;
891 short i = data.toShort(&convertOk);
892 if (convertOk)
893 return QVariant::fromValue<short>(i);
894 break;
895 }
896 case QMetaType::UShort:
897 {
898 klfDbg("ushort!") ;
899 ushort i = data.toUShort(&convertOk);
900 if (convertOk)
901 return QVariant::fromValue<ushort>(i);
902 break;
903 }
904 case QMetaType::Long:
905 {
906 klfDbg("long!") ;
907 long i = data.toLong(&convertOk);
908 if (convertOk)
909 return QVariant::fromValue<long>(i);
910 break;
911 }
912 case QMetaType::ULong:
913 {
914 klfDbg("ulong!") ;
915 ulong i = data.toULong(&convertOk);
916 if (convertOk)
917 return QVariant::fromValue<ulong>(i);
918 break;
919 }
920 case QMetaType::LongLong:
921 {
922 klfDbg("longlong!") ;
923 qlonglong i = data.toLongLong(&convertOk);
924 if (convertOk)
925 return QVariant::fromValue<qlonglong>(i);
926 break;
927 }
928 case QMetaType::ULongLong:
929 {
930 klfDbg("ulonglong!") ;
931 qulonglong i = data.toULongLong(&convertOk);
932 if (convertOk)
933 return QVariant::fromValue<qulonglong>(i);
934 break;
935 }
936 case QMetaType::Double:
937 {
938 klfDbg("double!") ;
939 double val = data.toDouble(&convertOk);
940 if (convertOk)
941 return QVariant::fromValue<double>(val);
942 break;
943 }
944 case QMetaType::Char:
945 {
946 klfDbg("char!") ;
947 if (data[0] == '\\') {
948 if (data.size() < 2)
949 break;
950 if (data[1] == '\\')
951 return QVariant::fromValue<char>('\\');
952 if (data.size() < 3)
953 break;
954 uint c = data.mid(1).toUInt(&convertOk, 16);
955 if (!convertOk)
956 break;
957 convertOk = false; // reset by default convertOk to false
958 if (c > 255)
959 break;
960 return QVariant::fromValue<char>( (char)c );
961 }
962 return QVariant::fromValue<char>( (char)data[0] );
963 }
964 case QMetaType::QChar:
965 {
966 klfDbg("QChar!") ;
967 if (data[0] == '\\') {
968 if (data.size() < 2)
969 break;
970 if (data[1] == '\\')
971 return QVariant::fromValue<QChar>(QChar('\\'));
972 if (data.size() < 3)
973 break;
974 uint c = data.mid(1).toUInt(&convertOk, 16);
975 if (!convertOk)
976 break;
977 convertOk = false; // reset by default convertOk to false
978 if (c > 255)
979 break;
980 return QVariant::fromValue<QChar>( QChar(c) );
981 }
982 return QVariant::fromValue<QChar>( QChar(data[0]) );
983 }
984 case QMetaType::QString:
985 {
986 klfDbg("qstring!") ;
987 QString s;
988 QByteArray chunk;
989 k = 0;
990 while (k < data.size()) {
991 if (data[k] != '\\') {
992 chunk += data[k];
993 ++k;
994 continue;
995 }
996 if (data[k] == '\\' && k+1 >= data.size()) {
997 chunk += '\\'; // backslash at end of data
998 ++k;
999 continue;
1000 }
1001 // not at end of data
1002 if (data[k+1] != 'x') {
1003 // backslash followed by something else than 'x', add that escaped 'something else'
1004 chunk += data[k+1];
1005 k += 2; // had to skip the backslash
1006 continue;
1007 }
1008 // pos k points on '\\', pos k+1 points on 'x'
1009 int nlen = -1;
1010 if (k+5 < data.size() && klf_is_hex_char(data[k+2]) && klf_is_hex_char(data[k+3])
1011 && klf_is_hex_char(data[k+4]) && klf_is_hex_char(data[k+5])) {
1012 nlen = 4; // 4-digit Unicode char
1013 }
1014 if (k+3 < data.size() && klf_is_hex_char(data[k+2]) && klf_is_hex_char(data[k+3])) {
1015 nlen = 2; // 2 last digits of 4-digit unicode char
1016 }
1017 if (nlen < 0) {
1018 // bad format, ignore the escape sequence.
1019 chunk += data[k];
1020 ++k;
1021 continue;
1022 }
1023 // decode this char
1024 ushort cval = data.mid(k+2, nlen).toUShort(&convertOk, 16);
1025 QChar ch(cval);
1026 // dump chunk into string, and add this char
1027 s += QString::fromLocal8Bit(chunk) + ch;
1028 // reset chunk
1029 chunk = QByteArray();
1030 // and advance the corresponding number of characters, point on fresh one
1031 // advance of what we read: backslash+'x' (=2) + number of digits
1032 k += 2 + nlen;
1033 }
1034 // dump remaining chunk
1035 s += QString::fromLocal8Bit(chunk);
1036 return QVariant::fromValue<QString>(s);
1037 }
1038 case QMetaType::QStringList:
1039 {
1040 klfDbg("qstringlist!") ;
1041 QList<QByteArray> sections = decaps_list(data);
1042
1043 // now we separated into bytearray sections. now read those into values.
1044 QStringList list;
1045 for (k = 0; k < sections.size(); ++k) {
1046 list << QString::fromUtf8(klfEscapedToData(sections[k]));
1047 }
1048
1049 return QVariant::fromValue<QStringList>(list);
1050 }
1051 case QMetaType::QUrl:
1052 {
1053 klfDbg("url!") ;
1054 return QVariant::fromValue<QUrl>(QUrl(QString::fromLocal8Bit(data), QUrl::TolerantMode));
1055 }
1056 case QMetaType::QByteArray:
1057 {
1058 klfDbg("qbytearray!") ;
1059 QByteArray value_ba = klfEscapedToData(data);
1060 return QVariant::fromValue<QByteArray>(value_ba);
1061 }
1062 case QMetaType::QDate:
1063 {
1064 klfDbg("qdate!") ;
1066 QDate date = QDate::fromString(s, Qt::SystemLocaleShortDate);
1067 if (!date.isValid()) date = QDate::fromString(s, Qt::ISODate);
1068 if (!date.isValid()) date = QDate::fromString(s, Qt::SystemLocaleLongDate);
1069 if (!date.isValid()) date = QDate::fromString(s, Qt::DefaultLocaleShortDate);
1070 if (!date.isValid()) date = QDate::fromString(s, Qt::TextDate);
1071 if (!date.isValid()) date = QDate::fromString(s, "dd-MM-yyyy");
1072 if (!date.isValid()) date = QDate::fromString(s, "dd.MM.yyyy");
1073 if (!date.isValid()) date = QDate::fromString(s, "dd MM yyyy");
1074 if (!date.isValid()) date = QDate::fromString(s, "yyyy-MM-dd");
1075 if (!date.isValid()) date = QDate::fromString(s, "yyyy.MM.dd");
1076 if (!date.isValid()) date = QDate::fromString(s, "yyyy MM dd");
1077 if (!date.isValid()) date = QDate::fromString(s, "yyyyMMdd");
1078 if (!date.isValid())
1079 break;
1080 return QVariant::fromValue<QDate>(date);
1081 }
1082 case QMetaType::QTime:
1083 {
1084 klfDbg("qtime!") ;
1086 QTime time = QTime::fromString(s, Qt::SystemLocaleShortDate);
1087 if (!time.isValid()) time = QTime::fromString(s, Qt::ISODate);
1088 if (!time.isValid()) time = QTime::fromString(s, Qt::SystemLocaleLongDate);
1089 if (!time.isValid()) time = QTime::fromString(s, Qt::DefaultLocaleShortDate);
1090 if (!time.isValid()) time = QTime::fromString(s, Qt::TextDate);
1091 if (!time.isValid()) time = QTime::fromString(s, "hh:mm:ss.z");
1092 if (!time.isValid()) time = QTime::fromString(s, "hh:mm:ss");
1093 if (!time.isValid()) time = QTime::fromString(s, "hh:mm:ss AP");
1094 if (!time.isValid()) time = QTime::fromString(s, "hh.mm.ss");
1095 if (!time.isValid()) time = QTime::fromString(s, "hh.mm.ss AP");
1096 if (!time.isValid()) time = QTime::fromString(s, "hh mm ss");
1097 if (!time.isValid()) time = QTime::fromString(s, "hh mm ss AP");
1098 if (!time.isValid()) time = QTime::fromString(s, "hhmmss");
1099 if (!time.isValid())
1100 break;
1101 return QVariant::fromValue<QTime>(time);
1102 }
1103 case QMetaType::QDateTime:
1104 {
1105 klfDbg("qdatetime!") ;
1107 QDateTime dt = QDateTime::fromString(s, Qt::SystemLocaleShortDate);
1108 if (!dt.isValid()) dt = QDateTime::fromString(s, Qt::ISODate);
1109 if (!dt.isValid()) dt = QDateTime::fromString(s, Qt::SystemLocaleLongDate);
1110 if (!dt.isValid()) dt = QDateTime::fromString(s, Qt::DefaultLocaleShortDate);
1111 if (!dt.isValid()) dt = QDateTime::fromString(s, Qt::TextDate);
1112 if (!dt.isValid()) dt = QDateTime::fromString(s, "dd-MM-yyyy hh:mm:ss");
1113 if (!dt.isValid()) dt = QDateTime::fromString(s, "dd-MM-yyyy hh.mm.ss");
1114 if (!dt.isValid()) dt = QDateTime::fromString(s, "dd.MM.yyyy hh:mm:ss");
1115 if (!dt.isValid()) dt = QDateTime::fromString(s, "dd.MM.yyyy hh.mm.ss");
1116 if (!dt.isValid()) dt = QDateTime::fromString(s, "dd MM yyyy hh mm ss");
1117 if (!dt.isValid()) dt = QDateTime::fromString(s, "yyyy-MM-dd hh:mm:ss");
1118 if (!dt.isValid()) dt = QDateTime::fromString(s, "yyyy-MM-dd hh.mm.ss");
1119 if (!dt.isValid()) dt = QDateTime::fromString(s, "yyyy.MM.dd hh:mm:ss");
1120 if (!dt.isValid()) dt = QDateTime::fromString(s, "yyyy.MM.dd hh.mm.ss");
1121 if (!dt.isValid()) dt = QDateTime::fromString(s, "yyyy MM dd hh mm ss");
1122 if (!dt.isValid()) dt = QDateTime::fromString(s, "yyyyMMddhhmmss");
1123 if (!dt.isValid())
1124 break;
1125 return QVariant::fromValue<QDateTime>(dt);
1126 }
1127 case QMetaType::QSize:
1128 {
1129 klfDbg("qsize!") ;
1131 if (szrx.indexIn(s) < 0)
1132 break;
1133 QStringList vals = szrx.capturedTexts();
1134 return QVariant::fromValue<QSize>(QSize(vals[SZRX_W].toInt(), vals[SZRX_H].toInt()));
1135 }
1136 case QMetaType::QPoint:
1137 {
1138 klfDbg("qpoint!") ;
1140 if (v2rx.indexIn(s) < 0)
1141 break;
1142 QStringList vals = v2rx.capturedTexts();
1143 return QVariant::fromValue<QPoint>(QPoint(vals[V2RX_X].toInt(), vals[V2RX_Y].toInt()));
1144 }
1145 case QMetaType::QRect:
1146 {
1147 klfDbg("qrect!") ;
1149 if (rectrx.indexIn(s) < 0)
1150 break;
1151 QStringList vals = rectrx.capturedTexts();
1152 if (vals[RECTRX_MIDDLESEP_PLUS] == "+" || vals[RECTRX_LASTSEP_X] == "x") {
1153 return QVariant::fromValue<QRect>(QRect( QPoint(vals[RECTRX_X1].toInt(), vals[RECTRX_Y1].toInt()),
1154 QSize(vals[RECTRX_X2orW].toInt(), vals[RECTRX_Y2orH].toInt()) ));
1155 }
1156 return QVariant::fromValue<QRect>(QRect( QPoint(vals[RECTRX_X1].toInt(), vals[RECTRX_Y1].toInt()),
1157 QPoint(vals[RECTRX_X2orW].toInt(), vals[RECTRX_Y2orH].toInt()) ));
1158 }
1159 case QMetaType::QColor:
1160 {
1161 klfDbg("qcolor!") ;
1162 QString colstr = QString::fromLocal8Bit(data.trimmed());
1163 // try our regexp
1164 if (colrx.indexIn(colstr) < 0) {
1165 klfDbg("color "<<colstr<<" does not match regexp="<<colrx.pattern()<<", trying named...") ;
1166 // try a named color
1167 QColor color; color.setNamedColor(colstr);
1168 // if we got a valid color, yepee
1169 if (color.isValid())
1170 return color;
1171 break;
1172 }
1173 // our regexp matched
1174 QStringList vals = colrx.capturedTexts();
1175 QColor color = QColor(vals[COLRX_R].toInt(), vals[COLRX_G].toInt(), vals[COLRX_B].toInt(), 255);
1176 if (!vals[COLRX_MAYBE_ALPHA].isEmpty())
1177 color.setAlpha(vals[COLRX_A].toInt());
1178 return QVariant::fromValue<QColor>(color);
1179 }
1180 case QMetaType::QFont:
1181 {
1182 klfDbg("qfont!") ;
1183 if (fontrx.indexIn(QString::fromLocal8Bit(data.trimmed())) < 0) {
1184 klfDbg("malformed font: "<<data);
1185 break;
1186 }
1187 QStringList vals = fontrx.capturedTexts();
1188 klfDbg("parsing font: data="<<data<<"; captured texts are: "<<vals );
1189
1190 QString family = vals[FONTRX_FAMILY].trimmed();
1191 QString weighttxt = vals[FONTRX_WEIGHT_TEXT];
1192 QString weightval = vals[FONTRX_WEIGHT_VALUE];
1193 QString styletxt = vals[FONTRX_STYLE_TEXT];
1194 QString ptsval = vals[FONTRX_POINTSIZE];
1195
1196 int weight = QFont::Normal;
1197 if (weighttxt == "Light") weight = QFont::Light;
1198 else if (weighttxt == "Normal") weight = QFont::Normal;
1199 else if (weighttxt == "DemiBold") weight = QFont::DemiBold;
1200 else if (weighttxt == "Bold") weight = QFont::Bold;
1201 else if (weighttxt == "Black") weight = QFont::Black;
1202 else if (weighttxt.startsWith("Wgt")) weight = weightval.toInt();
1203
1204
1205 QFont::Style style = QFont::StyleNormal;
1206 if (styletxt == "Normal") style = QFont::StyleNormal;
1207 else if (styletxt == "Italic") style = QFont::StyleItalic;
1208 else if (styletxt == "Oblique") style = QFont::StyleOblique;
1209
1210 int pt = -1;
1211 if (!ptsval.isEmpty())
1212 pt = ptsval.toInt();
1213
1214 QFont font(family, pt, weight);
1215 font.setStyle(style);
1216 return QVariant::fromValue<QFont>(font);
1217 }
1218 case QMetaType::QBrush:
1219 {
1220 klfDbg("qbrush!") ;
1221 if (brushrx.indexIn(QString::fromLocal8Bit(data.trimmed())) < 0) {
1222 klfDbg("malformed brush text: "<<data) ;
1223 break;
1224 }
1225 QStringList vals = brushrx.capturedTexts();
1226 QString style = vals[BRUSHRX_STYLE];
1227 // find brush style
1228 int k;
1229 bool style_found = false;
1230 for (k = 0; klf_brush_styles[k].brushStyle >= 0 && klf_brush_styles[k].style != NULL; ++k) {
1231 if (klf_brush_styles[k].style == style) {
1232 style_found = true;
1233 break;
1234 }
1235 }
1236 if (!style_found) {
1237 klfDbg("Can't find style"<<style<<" in brush style list!");
1238 break;
1239 }
1240 int qbrush_style = klf_brush_styles[k].brushStyle;
1241 // read the color and construct QBrush.
1242 QColor c = QColor(vals[BRUSHRX_R].toInt(), vals[BRUSHRX_G].toInt(),
1243 vals[BRUSHRX_B].toInt());
1244 if (!vals[BRUSHRX_A].isEmpty())
1245 c.setAlpha(vals[BRUSHRX_A].toInt());
1246 return QBrush(c, static_cast<Qt::BrushStyle>(qbrush_style));
1247 }
1248 case QMetaType::QTextFormat:
1249 {
1250 klfDbg("qtextformat!") ;
1251 int k;
1252 QList<QPair<QByteArray,QByteArray> > sections = decaps_map(data, true);
1253 if (sections.isEmpty()) {
1254 klfDbg("Invalid QTextFormat data.") ;
1255 break;
1256 }
1257 QPair<QByteArray,QByteArray> firstSection = sections.takeFirst();
1258 QString fmttype = QString::fromLatin1(firstSection.first);
1259 // find the format in our list
1260 for (k = 0; klf_text_format_formats[k].format != NULL; ++k)
1261 if (QString::compare(fmttype, QLatin1String(klf_text_format_formats[k].format),
1262 Qt::CaseInsensitive) == 0)
1263 break;
1264 if (klf_text_format_formats[k].format == NULL) {
1265 klfDbg("QTextFormat: Invalid format type: "<<fmttype) ;
1266 break;
1267 }
1268 int qtextformat_type = klf_text_format_formats[k].formatId;
1269
1270 // now decode the list of properties
1271 QTextFormat textformat(qtextformat_type);
1272 QList<QPair<QByteArray,QByteArray> >::const_iterator it;
1273 for (it = sections.begin(); it != sections.end(); ++it) {
1274 QByteArray key = (*it).first.trimmed();
1275 QByteArray value = (*it).second;
1276 klfDbg("QTextFormat: considering property pair key="<<key<<"; value="<<value) ;
1277 // see if the key is a keyword
1278 for (k = 0; klf_text_format_keywords[k].keyword != NULL; ++k)
1279 if (QString::compare(QLatin1String(klf_text_format_keywords[k].keyword),
1280 key, Qt::CaseInsensitive) == 0)
1281 break;
1282 if (klf_text_format_keywords[k].keyword != NULL) {
1283 // this is a keyword.
1284 klfDbg("QTextFormat: is keyword, propId="<<klf_text_format_keywords[k].propId<<", fixed_value="
1285 <<klf_text_format_keywords[k].fixed_value) ;
1286 textformat.setProperty(klf_text_format_keywords[k].propId,
1287 klf_text_format_keywords[k].fixed_value);
1288 continue;
1289 }
1290 // see if the key is a known property name
1291 for (k = 0; klf_text_format_props[k].key != NULL; ++k)
1292 if (QString::compare(QLatin1String(klf_text_format_props[k].key),
1293 key, Qt::CaseInsensitive) == 0)
1294 break;
1295 if (klf_text_format_props[k].key != NULL) {
1296 klfDbg("QTextFormat: is known property of type "<<klf_text_format_props[k].type) ;
1297 // load property propId, of type type
1298 QVariant vval = klfLoadVariantFromText(value, klf_text_format_props[k].type, "XML");
1299 textformat.setProperty(klf_text_format_props[k].propId, vval);
1300 continue;
1301 }
1302 // load generally-saved qvariant property
1303
1304 bool tointok = true;
1305 int propid = key.toInt(&tointok);
1306 if (!tointok) {
1307 qWarning()<<KLF_FUNC_NAME<<": QTextFormat bad format for general property key=value pair; "
1308 <<"key is not a numerical property ID, nor is it a known property name.";
1309 }
1310
1311 klfDbg("QTextFormat: property is not a known one. propid="<<propid) ;
1312
1313 // trim space beginning of string
1314 while (value.size() && QChar(value[0]).isSpace())
1315 value.remove(0, 1);
1316 int i;
1317 if (value.isEmpty() || !value.startsWith("[") || ((i = value.indexOf(']')) == -1)) {
1318 qWarning().nospace()<<KLF_FUNC_NAME<<": QTextFormat bad format for general property, value does "
1319 <<"not begin with \"[type-name]\".";
1320 continue;
1321 }
1322 QByteArray typenm = value.mid(1, i-1);
1323 QByteArray valuedata = value.mid(i+1);
1324 QVariant vval = klfLoadVariantFromText(valuedata, typenm);
1325 klfDbg("setting generalized property "<<propid<<" to value "<<vval) ;
1326 textformat.setProperty(propid, vval);
1327 }
1328 return textformat;
1329 }
1330 case QMetaType::QVariantList:
1331 {
1332 klfDbg("qvariantlist!") ;
1333 if (listOrMapDataTypeName == QLatin1String("XML")) {
1334 QDomElement el = parse_xml_wrapper(data, "variant-list");
1335 return klfLoadVariantListFromXML(el);
1336 } else {
1337 QList<QByteArray> sections = decaps_list(data);
1338
1339 // now we separated into bytearray sections. now read those into values.
1340 QVariantList list;
1341 for (k = 0; k < sections.size(); ++k) {
1342 QVariant val = klfLoadVariantFromText(sections[k], listOrMapDataTypeName);
1343 list << val;
1344 }
1345
1346 return QVariant::fromValue<QVariantList>(list);
1347 }
1348 }
1349 case QMetaType::QVariantMap:
1350 {
1351 klfDbg("qvariantmap!") ;
1352 if (listOrMapDataTypeName == QLatin1String("XML")) {
1353 QDomElement el = parse_xml_wrapper(data, "variant-map");
1354 return klfLoadVariantMapFromXML(el);
1355 } else {
1356 const QList<QPair<QByteArray,QByteArray> > sections = decaps_map(data);
1357 QVariantMap vmap;
1358 QList<QPair<QByteArray,QByteArray> >::const_iterator it;
1359 for (it = sections.begin(); it != sections.end(); ++it) {
1360 QString key = klfLoadVariantFromText((*it).first, "QString").toString();
1361 QVariant value = klfLoadVariantFromText((*it).second, listOrMapDataTypeName);
1362 vmap[key] = value;
1363 }
1364 return QVariant::fromValue<QVariantMap>(vmap);
1365 }
1366 }
1367 default:
1368 break;
1369 }
1370
1371 if (tname == "KLFEnumType") {
1372 // just load the integer value!
1373 KLFEnumType e;
1374 e.setSpecification(tspecification);
1375 e.setValue(data.toInt());
1376 return QVariant::fromValue<KLFEnumType>(e);
1377 }
1378
1379 klfDbg("other type or failed to load the good type!") ;
1380
1381 // maybe load a propertized object.
1383 // construct a default such wanted object of requried type
1384 QVariant value(QMetaType::type(dataTypeName), (const void*)NULL);
1386 const_cast<KLFAbstractPropertizedObject*>(static_cast<const KLFAbstractPropertizedObject*>(value.data()));
1387
1388 if (tspecification.size()) {
1389 KLFSpecifyableType * st =
1390 const_cast<KLFSpecifyableType*>(static_cast<const KLFSpecifyableType*>(value.data()));
1391 st->setSpecification(tspecification);
1392 }
1393
1394 bool hasfixedtypes = obj->hasFixedTypes();
1395
1396 klfDbg("loading an abstr.prop.obj: "<<obj) ;
1397 klfDbg("obj is of type "<<obj->objectKind()<<", fixedtypes="<<hasfixedtypes) ;
1398
1399 QVariantMap props = klfLoadVariantFromText(data, "QVariantMap", hasfixedtypes ? "QByteArray" : "XML").toMap();
1400 if (!hasfixedtypes) {
1401 obj->setAllProperties(props); // the properties are all as required
1402 return value;
1403 }
1404 // if we have fixed types, convert them all back from text (this is human-readable)
1405 QVariantMap propsconverted;
1406 for (QVariantMap::const_iterator it = props.begin(); it != props.end(); ++it) {
1407 QByteArray tn = obj->typeNameFor(it.key());
1408 // add type specification if needed
1409 QByteArray ts = obj->typeSpecificationFor(it.key());
1410 if (ts.size())
1411 tn += "/"+ts;
1412 propsconverted[it.key()] = klfLoadVariantFromText(it.value().toByteArray(), tn,
1413 "XML"); // in case of list/map values, we have used XML
1414 klfDbg("Loading property "<<it.key()<<" from saved text, value = "<<propsconverted[it.key()]) ;
1415 }
1416 props = propsconverted;
1417 obj->setAllProperties(props);
1418 return value;
1419 }
1420
1421 qWarning("klfLoadVariantFromText: Can't load a %s from %s !", dataTypeName, stringdata.constData());
1422 return QVariant();
1423}
1424
1425
1426
1427
1428
1429// ----------------------------------------------------
1430
1431
1432
1433
1434
1436{
1438
1439 QDomDocument doc = baseNode.ownerDocument();
1440
1441 for (QVariantMap::const_iterator it = vmap.begin(); it != vmap.end(); ++it) {
1442 QString key = it.key();
1443 QVariant value = it.value();
1444
1445 QDomElement pairNode = doc.createElement("pair");
1446 // * key
1447 QDomElement keyNode = doc.createElement("key");
1448 QDomText keyText = doc.createTextNode(key);
1449 keyNode.appendChild(keyText);
1450 pairNode.appendChild(keyNode);
1451 // * value data
1452 QDomElement vdataNode = doc.createElement("value");
1453 QString vtype = QLatin1String(value.typeName());
1454 if (vtype == "QVariantMap") {
1455 vdataNode.setAttribute(QLatin1String("type"), vtype);
1456 vdataNode = klfSaveVariantMapToXML(value.toMap(), vdataNode);
1457 } else if (vtype == "QVariantList") {
1458 vdataNode.setAttribute(QLatin1String("type"), vtype);
1459 vdataNode = klfSaveVariantListToXML(value.toList(), vdataNode);
1460 } else {
1461 QByteArray savedvtype;
1462 QDomText vdataText = doc.createTextNode(QString::fromLocal8Bit(klfSaveVariantToText(value, false, &savedvtype)));
1463 vdataNode.appendChild(vdataText);
1464 vdataNode.setAttribute(QLatin1String("type"), QString::fromUtf8(savedvtype));
1465 }
1466 pairNode.appendChild(vdataNode);
1467 // now append this pair to our list
1468 baseNode.appendChild(pairNode);
1469 }
1470 return baseNode;
1471}
1472
1474{
1476
1477 QVariantMap vmap;
1478
1479 QDomNode n;
1480 for (n = xmlNode.firstChild(); ! n.isNull(); n = n.nextSibling()) {
1481 QDomElement e = n.toElement(); // try to convert the node to an element.
1482 if ( e.isNull() || n.nodeType() != QDomNode::ElementNode )
1483 continue;
1484 if ( e.nodeName() != "pair" ) {
1485 klfWarning("Ignoring unexpected tag "<<e.nodeName()) ;
1486 continue;
1487 }
1488 // read this pair
1489 QString key;
1490 QByteArray valuetype;
1491 QByteArray valuedata;
1492 QDomElement valueNode;
1493 QDomNode nn;
1494 for (nn = e.firstChild(); ! nn.isNull(); nn = nn.nextSibling()) {
1495 klfDbg("inside <pair>: read node "<<nn.nodeName()) ;
1496 QDomElement ee = nn.toElement();
1497 if ( ee.isNull() || nn.nodeType() != QDomNode::ElementNode )
1498 continue;
1499 if ( ee.nodeName() == "key" ) {
1500 key = ee.text();
1501 continue;
1502 }
1503 if ( ee.nodeName() == "value" ) {
1504 // "local 8-bit" because klfLoadVariantFromText() assumes local 8-bit encoding
1505 valueNode = ee;
1506 valuedata = ee.text().toLocal8Bit();
1507 valuetype = ee.attribute("type").toUtf8();
1508 continue;
1509 }
1510 klfWarning("Ignoring unexpected tag "<<ee.nodeName()<<" in <pair>!") ;
1511 }
1512 QVariant value;
1513 if (valuetype == "QVariantMap") {
1514 value = QVariant::fromValue<QVariantMap>(klfLoadVariantMapFromXML(valueNode));
1515 } else if (valuetype == "QVariantList") {
1516 value = QVariant::fromValue<QVariantList>(klfLoadVariantListFromXML(valueNode));
1517 } else {
1518 value = klfLoadVariantFromText(valuedata, valuetype.constData());
1519 }
1520 // set this value in our variant map
1521 vmap[key] = value;
1522 }
1523 return vmap;
1524}
1525
1526
1527KLF_EXPORT QDomElement klfSaveVariantListToXML(const QVariantList& vlist, QDomElement baseNode)
1528{
1529 QDomDocument doc = baseNode.ownerDocument();
1530
1531 for (QVariantList::const_iterator it = vlist.begin(); it != vlist.end(); ++it) {
1532 QVariant value = *it;
1533
1534 QDomElement elNode = doc.createElement(QLatin1String("item"));
1535 QString vtype = QString::fromLatin1(value.typeName()); // "Latin1" encoding by convention
1536 // because type names do not have any special chars
1537 if (vtype == "QVariantMap") {
1538 elNode.setAttribute(QLatin1String("type"), vtype);
1539 elNode = klfSaveVariantMapToXML(value.toMap(), elNode);
1540 } else if (vtype == "QVariantList") {
1541 elNode.setAttribute(QLatin1String("type"), vtype);
1542 elNode = klfSaveVariantListToXML(value.toList(), elNode);
1543 } else {
1544 QByteArray savedvtype;
1545 QDomText vdataText = doc.createTextNode(QString::fromLocal8Bit(klfSaveVariantToText(value, false, &savedvtype)));
1546 elNode.appendChild(vdataText);
1547 elNode.setAttribute(QLatin1String("type"), QString::fromUtf8(savedvtype));
1548 }
1549 // now append this pair to our list
1550 //klfDbg( "... appending node!" ) ;
1551 baseNode.appendChild(elNode);
1552 }
1553
1554 return baseNode;
1555}
1556
1558{
1560
1561 QVariantList vlist;
1562
1563 QDomNode n;
1564 for (n = xmlNode.firstChild(); ! n.isNull(); n = n.nextSibling()) {
1565 QDomElement e = n.toElement(); // try to convert the node to an element.
1566 if ( e.isNull() || n.nodeType() != QDomNode::ElementNode )
1567 continue;
1568 if ( e.nodeName() != QLatin1String("item") ) {
1569 qWarning("%s: ignoring unexpected tag `%s'!\n", KLF_FUNC_NAME, qPrintable(e.nodeName()));
1570 continue;
1571 }
1572
1573 QString vtype = e.attribute(QLatin1String("type"));
1574
1575 QVariant value;
1576 if (vtype == QLatin1String("QVariantMap")) {
1577 value = QVariant::fromValue<QVariantMap>(klfLoadVariantMapFromXML(e));
1578 } else if (vtype == QLatin1String("QVariantList")) {
1579 value = QVariant::fromValue<QVariantList>(klfLoadVariantListFromXML(e));
1580 } else {
1581 value = klfLoadVariantFromText(e.text().toLocal8Bit(), vtype.toLatin1().constData());
1582 }
1583
1584 // set this value in our variant map
1585 vlist << value;
1586 }
1587 return vlist;
1588}
1589
1590
1591
1592// --------------------------------------------------
1593
1594
1596 : KLFFactoryBase(&pFactoryManager)
1597{
1598}
1600{
1601}
1602
1603// static
1604KLFFactoryManager KLFAbstractPropertizedObjectSaver::pFactoryManager;
1605
1606// static
1609{
1610 QList<KLFFactoryBase*> allFactories = pFactoryManager.registeredFactories();
1611 QString s;
1612 foreach (KLFFactoryBase * ff, allFactories) {
1614 if ((s = f->recognizeDataFormat(data)).size() != 0) {
1615 // recognized format
1616 if (format != NULL)
1617 *format = s;
1618 return f;
1619 }
1620 }
1621 // failed to recognize format
1622 if (format != NULL)
1623 *format = QString();
1624 return NULL;
1625}
1626
1627// static
1630{
1631 return dynamic_cast<KLFAbstractPropertizedObjectSaver*>(pFactoryManager.findFactoryFor(format));
1632}
1633
1634// this is not a class member
1635KLFBaseFormatsPropertizedObjectSaver __klf_baseformats_pobj_saver; // this will automatically register it...
1636
1637
1639{
1643 KLF_ASSERT_NOT_NULL(saver, "Can't find object saver for format="<<format<<" !", return QByteArray(); ) ;
1644 return saver->save(obj, format);
1645}
1646
1648{
1651 QString f = format;
1652 if (f.isEmpty()) { // need to recognize format
1654 KLF_ASSERT_CONDITION(!f.isEmpty(), "Can't recognize data format!", return false; ) ;
1655 } else {
1657 }
1658 KLF_ASSERT_NOT_NULL(saver, "Can't find object saver for format="<<f<<" !", return false; ) ;
1659 return saver->load(data, obj, f);
1660}
1661
1662
An abstract object characterized by properties.
Definition klfpobj.h:59
virtual bool hasFixedTypes() const
Definition klfpobj.h:129
virtual QByteArray typeNameFor(const QString &property) const
Corresonding type for the given property.
Definition klfpobj.h:135
virtual QString objectKind() const =0
A string representing this object type.
virtual bool setAllProperties(const QMap< QString, QVariant > &data)
Convenience function to load a set of property values.
Definition klfpobj.cpp:61
virtual QByteArray typeSpecificationFor(const QString &property) const
A type specification for the given property.
Definition klfpobj.h:144
virtual QMap< QString, QVariant > allProperties() const
Convenience function to retrieve all properties.
Definition klfpobj.cpp:51
Inherit this class to implement a custom saver for KLFAbstractPropertizedObjects.
virtual QString recognizeDataFormat(const QByteArray &data) const =0
virtual bool load(const QByteArray &data, KLFAbstractPropertizedObject *obj, const QString &format)=0
static KLFAbstractPropertizedObjectSaver * findSaverFor(const QString &format)
static KLFAbstractPropertizedObjectSaver * findRecognizedFormat(const QByteArray &data, QString *format=NULL)
virtual QByteArray save(const KLFAbstractPropertizedObject *obj, const QString &format)=0
int value() const
Definition klfpobj.h:172
bool setSpecification(const QByteArray &data)
Definition klfpobj.h:205
void setValue(int v)
Definition klfpobj.h:176
Base class for factories.
Definition klffactory.h:41
A base abstract factory manager class.
Definition klffactory.h:92
KLFFactoryBase * findFactoryFor(const QString &objType)
QList< KLFFactoryBase * > registeredFactories()
Definition klffactory.h:107
static bool isRegistered(const char *name)
Definition klfpobj.h:905
static bool isRegistered(const char *name)
Definition klfpobj.h:947
virtual bool setSpecification(const QByteArray &data)=0
virtual QByteArray specification() const =0
int propId
KLFBaseFormatsPropertizedObjectSaver __klf_baseformats_pobj_saver
const char * style
#define KLF_BRUSH_STYLE(sty)
int brushStyle
KLF_EXPORT QByteArray klfEscapedToData(const QByteArray &data, char escapechar)
int formatId
const char * keyword
#define KLF_TEXT_FORMAT_PROP(p, type)
KLF_EXPORT QDomElement klfSaveVariantListToXML(const QVariantList &vlist, QDomElement baseNode)
Lossless save of full list to XML with type information.
const char * format
QVariant fixed_value
const char * type
#define RX_INT
KLF_EXPORT QDomElement klfSaveVariantMapToXML(const QVariantMap &vmap, QDomElement baseNode)
Lossless save of full map to XML with type information.
#define RX_COORD_SEP
KLF_EXPORT QVariant klfLoadVariantFromText(const QByteArray &stringdata, const char *dataTypeName, const char *listOrMapDataTypeName)
KLF_EXPORT bool klfLoad(const QByteArray &data, KLFAbstractPropertizedObject *obj, const QString &format)
KLF_EXPORT QByteArray klfDataToEscaped(const QByteArray &value_ba, char escapechar)
KLF_EXPORT QByteArray klfSaveVariantToText(const QVariant &value, bool saveListAndMapsAsXML, QByteArray *savedType, QByteArray *savedListOrMapType)
KLF_EXPORT QVariantMap klfLoadVariantMapFromXML(const QDomElement &xmlNode)
Load a map saved with klfSaveVariantMapToXML()
const char * key
#define RX_SIZE_SEP
KLF_EXPORT QVariantList klfLoadVariantListFromXML(const QDomElement &xmlNode)
Load a list saved with klfSaveVariantListToXML()
KLF_EXPORT QByteArray klfSave(const KLFAbstractPropertizedObject *obj, const QString &format)
#define KLF_TEXT_FORMAT_FORMAT(fmt)
#define KLF_DEBUG_TIME_BLOCK(msg)
Utility to time the execution of a block.
Definition klfdebug.h:151
#define klfWarning(streamableItems)
Definition klfdebug.h:171
#define KLF_DEBUG_BLOCK(msg)
Utility to debug the execution of a block.
Definition klfdebug.h:152
#define KLF_ASSERT_NOT_NULL(ptr, msg, failaction)
Asserting Non-NULL pointers (NON-FATAL)
Definition klfdebug.h:210
#define KLF_ASSERT_CONDITION(expr, msg, failaction)
Asserting Conditions (NON-FATAL)
Definition klfdebug.h:201
#define KLF_FUNC_NAME
Definition klfdebug.h:194
#define klfDbg(streamableItems)
print debug stream items
Definition klfdebug.h:158
Base declarations for klatexformula and some utilities.
#define klfFmtCC
Definition klfdefs.h:61
#define KLF_EXPORT
Definition klfdefs.h:41
const QColor & color() const
const QMatrix & matrix() const
Qt::BrushStyle style() const
const char * constData() const
bool contains(char ch) const
int indexOf(char ch, int from) const
bool isEmpty() const
bool isNull() const
QByteArray left(int len) const
QByteArray mid(int pos, int len) const
QByteArray number(int n, int base)
QByteArray & remove(int pos, int len)
QByteArray & replace(int pos, int len, const char *after)
int size() const
bool startsWith(const QByteArray &ba) const
double toDouble(bool *ok) const
int toInt(bool *ok, int base) const
long toLong(bool *ok, int base) const
qlonglong toLongLong(bool *ok, int base) const
QByteArray toLower() const
short toShort(bool *ok, int base) const
uint toUInt(bool *ok, int base) const
ulong toULong(bool *ok, int base) const
qulonglong toULongLong(bool *ok, int base) const
ushort toUShort(bool *ok, int base) const
QByteArray trimmed() const
ushort unicode() const
int alpha() const
int blue() const
int green() const
bool isValid() const
int red() const
void setAlpha(int alpha)
void setNamedColor(const QString &name)
void setVersion(int v)
QDate fromString(const QString &string, Qt::DateFormat format)
bool isValid() const
QDateTime fromString(const QString &string, Qt::DateFormat format)
bool isValid() const
QDomElement createElement(const QString &tagName)
QDomText createTextNode(const QString &value)
QByteArray toByteArray(int indent) const
QString attribute(const QString &name, const QString &defValue) const
void setAttribute(const QString &name, const QString &value)
QString text() const
QDomNode appendChild(const QDomNode &newChild)
QDomNode firstChild() const
bool isNull() const
QDomNode nextSibling() const
QString nodeName() const
NodeType nodeType() const
QDomDocument ownerDocument() const
QDomElement toElement() const
QString family() const
void setStyle(Style style)
Style style() const
int weight() const
void append(const T &value)
iterator begin()
iterator end()
bool isEmpty() const
int size() const
T takeFirst()
const Key & key() const
const T & value() const
iterator begin()
iterator end()
bool isIdentity() const
int type(const char *typeName)
int x() const
int y() const
int height() const
int left() const
int top() const
int width() const
QStringList capturedTexts() const
int indexIn(const QString &str, int offset, CaretMode caretMode) const
QString pattern() const
void setMinimal(bool minimal)
int height() const
int width() const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const
int compare(const QString &other, Qt::CaseSensitivity cs) const
QString fromLatin1(const char *str, int size)
QString fromLocal8Bit(const char *str, int size)
QString fromUtf8(const char *str, int size)
bool isEmpty() const
int length() const
QString mid(int position, int n) const
QString number(int n, int base)
QString & replace(int position, int n, QChar after)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
int toInt(bool *ok, int base) const
QByteArray toLatin1() const
QByteArray toLocal8Bit() const
QString toUpper() const
QByteArray toUtf8() const
bool canEncode(QChar ch) const
QTextCodec * codecForLocale()
QByteArray fromUnicode(const QString &str) const
QMap< int, QVariant > properties() const
void setProperty(int propertyId, const QVariant &value)
int type() const
QTime fromString(const QString &string, Qt::DateFormat format)
bool isValid() const
QByteArray toEncoded(FormattingOptions options) const
bool isNull() const
bool isValid() const
bool toBool() const
QChar toChar() const
QList< QVariant > toList() const
QMap< QString, QVariant > toMap() const
QPoint toPoint() const
QRect toRect() const
QSize toSize() const
QString toString() const
QStringList toStringList() const
QUrl toUrl() const
Type type() const
const char * typeName() const
T value() const

Generated by doxygen 1.9.7