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