1 /* This file is part of the KDE project
2 Copyright (C) 2004-2006 David Faure <faure@kde.org>
3 Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
4 Copyright (C) 2010 Jarosław Staniek <staniek@kde.org>
5 Copyright (C) 2011 Pierre Ducroquet <pinaraf@pinaraf.info>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library 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 GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22 // clazy:excludeall=qstring-arg
23 #include "KoGenStyle.h"
24 #include "KoGenStyles.h"
25
26 #include <QTextLength>
27
28 #include <KoXmlWriter.h>
29
30 #include <float.h>
31
32 #include <OdfDebug.h>
33
34 // Returns -1, 0 (equal) or 1
compareMap(const QMap<QString,QString> & map1,const QMap<QString,QString> & map2)35 static int compareMap(const QMap<QString, QString>& map1, const QMap<QString, QString>& map2)
36 {
37 QMap<QString, QString>::const_iterator it = map1.constBegin();
38 QMap<QString, QString>::const_iterator oit = map2.constBegin();
39 for (; it != map1.constEnd(); ++it, ++oit) { // both maps have been checked for size already
40 if (it.key() != oit.key())
41 return it.key() < oit.key() ? -1 : + 1;
42 if (it.value() != oit.value())
43 return it.value() < oit.value() ? -1 : + 1;
44 }
45 return 0; // equal
46 }
47
48
KoGenStyle(Type type,const char * familyName,const QString & parentName)49 KoGenStyle::KoGenStyle(Type type, const char* familyName,
50 const QString& parentName)
51 : m_type(type), m_familyName(familyName), m_parentName(parentName),
52 m_autoStyleInStylesDotXml(false), m_defaultStyle(false)
53 {
54 switch (type) {
55 case TextStyle:
56 case TextAutoStyle:
57 m_propertyType = TextType;
58 break;
59 case ParagraphStyle:
60 case ParagraphAutoStyle:
61 m_propertyType = ParagraphType;
62 break;
63 case GraphicStyle:
64 case GraphicAutoStyle:
65 m_propertyType = GraphicType;
66 break;
67 case SectionStyle:
68 case SectionAutoStyle:
69 m_propertyType = SectionType;
70 break;
71 case RubyStyle:
72 case RubyAutoStyle:
73 m_propertyType = RubyType;
74 break;
75 case TableStyle:
76 case TableAutoStyle:
77 m_propertyType = TableType;
78 break;
79 case TableColumnStyle:
80 case TableColumnAutoStyle:
81 m_propertyType = TableColumnType;
82 break;
83 case TableRowStyle:
84 case TableRowAutoStyle:
85 m_propertyType = TableRowType;
86 break;
87 case TableCellStyle:
88 case TableCellAutoStyle:
89 m_propertyType = TableCellType;
90 break;
91 case PresentationStyle:
92 case PresentationAutoStyle:
93 m_propertyType = PresentationType;
94 break;
95 case DrawingPageStyle:
96 case DrawingPageAutoStyle:
97 m_propertyType = DrawingPageType;
98 break;
99 case ChartStyle:
100 case ChartAutoStyle:
101 m_propertyType = ChartType;
102 break;
103 default:
104 m_propertyType = DefaultType;
105 break;
106 }
107 }
108
~KoGenStyle()109 KoGenStyle::~KoGenStyle()
110 {
111 }
112
113 /*
114 * The order of this list is important; e.g. a graphic-properties must
115 * precede a text-properties always. See the Relax NG to check the order.
116 */
117 static const KoGenStyle::PropertyType s_propertyTypes[] = {
118 KoGenStyle::DefaultType,
119 KoGenStyle::SectionType,
120 KoGenStyle::RubyType,
121 KoGenStyle::TableType,
122 KoGenStyle::TableColumnType,
123 KoGenStyle::TableRowType,
124 KoGenStyle::TableCellType,
125 KoGenStyle::DrawingPageType,
126 KoGenStyle::ChartType,
127 KoGenStyle::GraphicType,
128 KoGenStyle::ParagraphType,
129 KoGenStyle::TextType,
130 };
131
132 static const char* const s_propertyNames[] = {
133 0,
134 "style:section-properties",
135 "style:ruby-properties",
136 "style:table-properties",
137 "style:table-column-properties",
138 "style:table-row-properties",
139 "style:table-cell-properties",
140 "style:drawing-page-properties",
141 "style:chart-properties",
142 "style:graphic-properties",
143 "style:paragraph-properties",
144 "style:text-properties"
145 };
146
147 static const int s_propertyNamesCount = sizeof(s_propertyNames) / sizeof(*s_propertyNames);
148
propertyTypeByElementName(const char * propertiesElementName)149 static KoGenStyle::PropertyType propertyTypeByElementName(const char* propertiesElementName)
150 {
151 for (int i = 0; i < s_propertyNamesCount; ++i) {
152 if (qstrcmp(s_propertyNames[i], propertiesElementName) == 0) {
153 return s_propertyTypes[i];
154 }
155 }
156 return KoGenStyle::DefaultType;
157 }
158
writeStyleProperties(KoXmlWriter * writer,PropertyType type,const KoGenStyle * parentStyle) const159 void KoGenStyle::writeStyleProperties(KoXmlWriter* writer, PropertyType type,
160 const KoGenStyle* parentStyle) const
161 {
162 const char* elementName = 0;
163 for (int i=0; i<s_propertyNamesCount; ++i) {
164 if (s_propertyTypes[i] == type) {
165 elementName = s_propertyNames[i];
166 }
167 }
168 Q_ASSERT(elementName);
169 const StyleMap& map = m_properties[type];
170 const StyleMap& mapChild = m_childProperties[type];
171 if (!map.isEmpty() || !mapChild.isEmpty()) {
172 writer->startElement(elementName);
173 QMap<QString, QString>::const_iterator it = map.constBegin();
174 const QMap<QString, QString>::const_iterator end = map.constEnd();
175 for (; it != end; ++it) {
176 if (!parentStyle || parentStyle->property(it.key(), type) != it.value())
177 writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
178 }
179 QMap<QString, QString>::const_iterator itChild = mapChild.constBegin();
180 const QMap<QString, QString>::const_iterator endChild = mapChild.constEnd();
181 for (; itChild != endChild; ++itChild) {
182 if (!parentStyle || parentStyle->childProperty(itChild.key(), type) != itChild.value())
183 writer->addCompleteElement(itChild.value().toUtf8());
184 }
185 writer->endElement();
186 }
187 }
188
writeStyle(KoXmlWriter * writer,const KoGenStyles & styles,const char * elementName,const QString & name,const char * propertiesElementName,bool closeElement,bool drawElement) const189 void KoGenStyle::writeStyle(KoXmlWriter* writer, const KoGenStyles& styles, const char* elementName, const QString& name, const char* propertiesElementName, bool closeElement, bool drawElement) const
190 {
191 //debugOdf <<"writing out style" << name <<" display-name=" << m_attributes["style:display-name"] <<" family=" << m_familyName;
192 writer->startElement(elementName);
193 const KoGenStyle* parentStyle = 0;
194 if (!m_defaultStyle) {
195 if (!drawElement)
196 writer->addAttribute("style:name", name);
197 else
198 writer->addAttribute("draw:name", name);
199 if (!m_parentName.isEmpty()) {
200 Q_ASSERT(!m_familyName.isEmpty());
201 parentStyle = styles.style(m_parentName, m_familyName);
202 if (parentStyle && m_familyName.isEmpty()) {
203 // get family from parent style, just in case
204 // Note: this is saving code, don't convert to attributeNS!
205 const_cast<KoGenStyle *>(this)->
206 m_familyName = parentStyle->attribute("style:family").toLatin1();
207 //debugOdf <<"Got familyname" << m_familyName <<" from parent";
208 }
209 if (parentStyle && !parentStyle->isDefaultStyle())
210 writer->addAttribute("style:parent-style-name", m_parentName);
211 }
212 } else { // default-style
213 Q_ASSERT(qstrcmp(elementName, "style:default-style") == 0);
214 Q_ASSERT(m_parentName.isEmpty());
215 }
216 if (!m_familyName.isEmpty())
217 const_cast<KoGenStyle *>(this)->
218 addAttribute("style:family", QString::fromLatin1(m_familyName));
219 else {
220 if (qstrcmp(elementName, "style:style") == 0)
221 warnOdf << "User style " << name << " is without family - invalid. m_type=" << m_type;
222 }
223
224 #if 0 // #ifndef NDEBUG
225 debugOdf << "style:" << name;
226 printDebug();
227 if (parentStyle) {
228 debugOdf << " parent:" << m_parentName;
229 parentStyle->printDebug();
230 }
231 #endif
232
233 // Write attributes [which differ from the parent style]
234 // We only look at the direct parent style because we assume
235 // that styles are fully specified, i.e. the inheritance is
236 // only in the final file, not in the caller's code.
237 QMap<QString, QString>::const_iterator it = m_attributes.constBegin();
238 for (; it != m_attributes.constEnd(); ++it) {
239 bool writeit = true;
240 if (parentStyle && it.key() != "style:family" // always write the family out
241 && parentStyle->attribute(it.key()) == it.value())
242 writeit = false;
243 if (writeit)
244 writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
245 }
246 bool createPropertiesTag = propertiesElementName && propertiesElementName[0] != '\0';
247 KoGenStyle::PropertyType i = KoGenStyle::DefaultType;
248 KoGenStyle::PropertyType defaultPropertyType = KoGenStyle::DefaultType;
249 if (createPropertiesTag)
250 defaultPropertyType = propertyTypeByElementName(propertiesElementName);
251 if (!m_properties[i].isEmpty() ||
252 !m_childProperties[defaultPropertyType].isEmpty() ||
253 !m_properties[defaultPropertyType].isEmpty()) {
254 if (createPropertiesTag)
255 writer->startElement(propertiesElementName); // e.g. paragraph-properties
256 it = m_properties[i].constBegin();
257 for (; it != m_properties[i].constEnd(); ++it) {
258 if (!parentStyle || parentStyle->property(it.key(), i) != it.value())
259 writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
260 }
261 //write the explicitly-defined properties that are the same type as the default,
262 //but only if defaultPropertyType is Text, Paragraph, or GraphicType
263 if (defaultPropertyType != 0) {
264 it = m_properties[defaultPropertyType].constBegin();
265 for (; it != m_properties[defaultPropertyType].constEnd(); ++it) {
266 if (!parentStyle || parentStyle->property(it .key(), defaultPropertyType) != it.value())
267 writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
268 }
269 }
270 //write child elements of the properties elements
271 it = m_childProperties[defaultPropertyType].constBegin();
272 for (; it != m_childProperties[defaultPropertyType].constEnd(); ++it) {
273 if (!parentStyle || parentStyle->childProperty(it.key(), defaultPropertyType) != it.value()) {
274 writer->addCompleteElement(it.value().toUtf8());
275 }
276 }
277 if (createPropertiesTag)
278 writer->endElement();
279 }
280
281 // now write out any other properties elements
282 //start with i=1 to skip the defaultType that we already took care of
283 for (int i = 1; i < s_propertyNamesCount; ++i) {
284 //skip any properties that are the same as the defaultType
285 if (s_propertyTypes[i] != defaultPropertyType) {
286 writeStyleProperties(writer, s_propertyTypes[i], parentStyle);
287 }
288 }
289
290 //write child elements that aren't in any of the properties elements
291 i = KoGenStyle::StyleChildElement;
292 it = m_properties[i].constBegin();
293 for (; it != m_properties[i].constEnd(); ++it) {
294 if (!parentStyle || parentStyle->property(it.key(), i) != it.value()) {
295 writer->addCompleteElement(it.value().toUtf8());
296 }
297 }
298
299 // And now the style maps
300 for (int i = 0; i < m_maps.count(); ++i) {
301 bool writeit = true;
302 if (parentStyle && compareMap(m_maps[i], parentStyle->m_maps[i]) == 0)
303 writeit = false;
304 if (writeit) {
305 writer->startElement("style:map");
306 QMap<QString, QString>::const_iterator it = m_maps[i].constBegin();
307 for (; it != m_maps[i].constEnd(); ++it) {
308 writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
309 }
310 writer->endElement(); // style:map
311 }
312 }
313 if (closeElement)
314 writer->endElement();
315 }
316
addPropertyPt(const QString & propName,qreal propValue,PropertyType type)317 void KoGenStyle::addPropertyPt(const QString& propName, qreal propValue, PropertyType type)
318 {
319 if (type == DefaultType) {
320 type = m_propertyType;
321 }
322 QString str;
323 str.setNum(propValue, 'f', DBL_DIG);
324 str += "pt";
325 m_properties[type].insert(propName, str);
326 }
327
addPropertyLength(const QString & propName,const QTextLength & propValue,PropertyType type)328 void KoGenStyle::addPropertyLength(const QString& propName, const QTextLength &propValue, PropertyType type)
329 {
330 if (type == DefaultType) {
331 type = m_propertyType;
332 }
333 if (propValue.type() == QTextLength::FixedLength) {
334 return addPropertyPt(propName, propValue.rawValue(), type);
335 } else {
336 QString str;
337 str.setNum((int) propValue.rawValue());
338 str += '%';
339 m_properties[type].insert(propName, str);
340 }
341 }
342
addAttributePt(const QString & attrName,qreal attrValue)343 void KoGenStyle::addAttributePt(const QString& attrName, qreal attrValue)
344 {
345 QString str;
346 str.setNum(attrValue, 'f', DBL_DIG);
347 str += "pt";
348 m_attributes.insert(attrName, str);
349 }
350
addAttributePercent(const QString & attrName,qreal value)351 void KoGenStyle::addAttributePercent(const QString &attrName, qreal value)
352 {
353 QByteArray str;
354 str.setNum(value, 'f', FLT_DIG);
355 str += '%';
356 addAttribute(attrName, str.data());
357 }
358
addAttributePercent(const QString & attrName,int value)359 void KoGenStyle::addAttributePercent(const QString &attrName, int value)
360 {
361 QByteArray str;
362 str.setNum(value);
363 str += '%';
364 addAttribute(attrName, str.data());
365 }
366
addStyleMap(const QMap<QString,QString> & styleMap)367 void KoGenStyle::addStyleMap(const QMap<QString, QString>& styleMap)
368 {
369 // check, if already present
370 for (int i = 0 ; i < m_maps.count() ; ++i) {
371 if (m_maps[i].count() == styleMap.count()) {
372 int comp = compareMap(m_maps[i], styleMap);
373 if (comp == 0)
374 return;
375 }
376 }
377 m_maps.append(styleMap);
378 }
379
380
381 #ifndef NDEBUG
printDebug() const382 void KoGenStyle::printDebug() const
383 {
384 int i = DefaultType;
385 debugOdf << m_properties[i].count() << " properties.";
386 for (QMap<QString, QString>::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) {
387 debugOdf << "" << it.key() << " =" << it.value();
388 }
389 i = TextType;
390 debugOdf << m_properties[i].count() << " text properties.";
391 for (QMap<QString, QString>::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) {
392 debugOdf << "" << it.key() << " =" << it.value();
393 }
394 i = ParagraphType;
395 debugOdf << m_properties[i].count() << " paragraph properties.";
396 for (QMap<QString, QString>::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) {
397 debugOdf << "" << it.key() << " =" << it.value();
398 }
399 i = TextType;
400 debugOdf << m_childProperties[i].count() << " text child elements.";
401 for (QMap<QString, QString>::ConstIterator it = m_childProperties[i].constBegin(); it != m_childProperties[i].constEnd(); ++it) {
402 debugOdf << "" << it.key() << " =" << it.value();
403 }
404 i = ParagraphType;
405 debugOdf << m_childProperties[i].count() << " paragraph child elements.";
406 for (QMap<QString, QString>::ConstIterator it = m_childProperties[i].constBegin(); it != m_childProperties[i].constEnd(); ++it) {
407 debugOdf << "" << it.key() << " =" << it.value();
408 }
409 debugOdf << m_attributes.count() << " attributes.";
410 for (QMap<QString, QString>::ConstIterator it = m_attributes.constBegin(); it != m_attributes.constEnd(); ++it) {
411 debugOdf << "" << it.key() << " =" << it.value();
412 }
413 debugOdf << m_maps.count() << " maps.";
414 for (int i = 0; i < m_maps.count(); ++i) {
415 debugOdf << "map" << i << ":";
416 for (QMap<QString, QString>::ConstIterator it = m_maps[i].constBegin(); it != m_maps[i].constEnd(); ++it) {
417 debugOdf << "" << it.key() << " =" << it.value();
418 }
419 }
420 debugOdf;
421 }
422 #endif
423
operator <(const KoGenStyle & other) const424 bool KoGenStyle::operator<(const KoGenStyle &other) const
425 {
426 if (m_type != other.m_type) return m_type < other.m_type;
427 if (m_parentName != other.m_parentName) return m_parentName < other.m_parentName;
428 if (m_familyName != other.m_familyName) return m_familyName < other.m_familyName;
429 if (m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml) return m_autoStyleInStylesDotXml;
430 for (uint i = 0 ; i <= LastPropertyType; ++i) {
431 if (m_properties[i].count() != other.m_properties[i].count()) {
432 return m_properties[i].count() < other.m_properties[i].count();
433 }
434 if (m_childProperties[i].count() != other.m_childProperties[i].count()) {
435 return m_childProperties[i].count() < other.m_childProperties[i].count();
436 }
437 }
438 if (m_attributes.count() != other.m_attributes.count()) return m_attributes.count() < other.m_attributes.count();
439 if (m_maps.count() != other.m_maps.count()) return m_maps.count() < other.m_maps.count();
440 // Same number of properties and attributes, no other choice than iterating
441 for (uint i = 0 ; i <= LastPropertyType; ++i) {
442 int comp = compareMap(m_properties[i], other.m_properties[i]);
443 if (comp != 0)
444 return comp < 0;
445 }
446 for (uint i = 0 ; i <= LastPropertyType; ++i) {
447 int comp = compareMap(m_childProperties[i], other.m_childProperties[i]);
448 if (comp != 0)
449 return comp < 0;
450 }
451 int comp = compareMap(m_attributes, other.m_attributes);
452 if (comp != 0)
453 return comp < 0;
454 for (int i = 0 ; i < m_maps.count() ; ++i) {
455 int comp = compareMap(m_maps[i], other.m_maps[i]);
456 if (comp != 0)
457 return comp < 0;
458 }
459 return false;
460 }
461
operator ==(const KoGenStyle & other) const462 bool KoGenStyle::operator==(const KoGenStyle &other) const
463 {
464 if (m_type != other.m_type) return false;
465 if (m_parentName != other.m_parentName) return false;
466 if (m_familyName != other.m_familyName) return false;
467 if (m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml) return false;
468 for (uint i = 0 ; i <= LastPropertyType; ++i) {
469 if (m_properties[i].count() != other.m_properties[i].count()) {
470 return false;
471 }
472 if (m_childProperties[i].count() != other.m_childProperties[i].count()) {
473 return false;
474 }
475 }
476 if (m_attributes.count() != other.m_attributes.count()) return false;
477 if (m_maps.count() != other.m_maps.count()) return false;
478 // Same number of properties and attributes, no other choice than iterating
479 for (uint i = 0 ; i <= LastPropertyType; ++i) {
480 int comp = compareMap(m_properties[i], other.m_properties[i]);
481 if (comp != 0)
482 return false;
483 }
484 for (uint i = 0 ; i <= LastPropertyType; ++i) {
485 int comp = compareMap(m_childProperties[i], other.m_childProperties[i]);
486 if (comp != 0)
487 return false;
488 }
489 int comp = compareMap(m_attributes, other.m_attributes);
490 if (comp != 0)
491 return false;
492 for (int i = 0 ; i < m_maps.count() ; ++i) {
493 int comp = compareMap(m_maps[i], other.m_maps[i]);
494 if (comp != 0)
495 return false;
496 }
497 return true;
498 }
499
isEmpty() const500 bool KoGenStyle::isEmpty() const
501 {
502 if (!m_attributes.isEmpty() || ! m_maps.isEmpty())
503 return false;
504 for (uint i = 0 ; i <= LastPropertyType; ++i)
505 if (! m_properties[i].isEmpty())
506 return false;
507 return true;
508 }
509
copyPropertiesFromStyle(const KoGenStyle & sourceStyle,KoGenStyle & targetStyle,PropertyType type)510 void KoGenStyle::copyPropertiesFromStyle(const KoGenStyle &sourceStyle, KoGenStyle &targetStyle, PropertyType type)
511 {
512 if (type == DefaultType) {
513 type = sourceStyle.m_propertyType;
514 }
515
516 const StyleMap& map = sourceStyle.m_properties[type];
517 if (!map.isEmpty()) {
518 QMap<QString, QString>::const_iterator it = map.constBegin();
519 const QMap<QString, QString>::const_iterator end = map.constEnd();
520 for (; it != end; ++it) {
521 targetStyle.addProperty(it.key(), it.value(), type);
522 }
523 }
524 }
525