1 /*
2  *  Copyright (c) 2010 Sebastian Sauer <sebsauer@kdab.com>
3  *  Copyright (c) 2010 Carlos Licea <carlos@kdab.com>
4  *  Copyright (c) 2014 Inge Wallin <inge@lysator.liu.se>
5  *
6  *  This library is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU Lesser General Public License as published
8  *  by the Free Software Foundation; either version 2.1 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 // Own
22 #include "KoOdfChartWriter.h"
23 
24 // libstdc++
25 #include <algorithm> // For std:find()
26 
27 // Calligra
28 #include <KoStore.h>
29 #include <KoXmlWriter.h>
30 #include <KoOdfWriteStore.h>
31 #include <KoStoreDevice.h>
32 #include <KoGenStyles.h>
33 #include <KoGenStyle.h>
34 
35 #include "Odf2Debug.h"
36 #include <Charting.h>
37 #include "NumberFormatParser.h"
38 
39 
40 // Print the content of generated content.xml to the console for debugging purpose
41 //#define CONTENTXML_DEBUG
42 
43 using namespace KoChart;
44 
KoOdfChartWriter(KoChart::Chart * chart)45 KoOdfChartWriter::KoOdfChartWriter(KoChart::Chart* chart)
46     : m_x(0)
47     , m_y(0)
48     , m_width(0)
49     , m_height(0)
50     , m_end_x(0)
51     , m_end_y(0)
52     , m_chart(chart)
53     , sheetReplacement(true)
54     , paletteIsSet(false)
55 {
56     Q_ASSERT(m_chart);
57     m_drawLayer = false;
58 }
59 
~KoOdfChartWriter()60 KoOdfChartWriter::~KoOdfChartWriter()
61 {
62 }
63 
64 
65 // Takes a Excel cellrange and translates it into a ODF cellrange
normalizeCellRange(QString range)66 QString KoOdfChartWriter::normalizeCellRange(QString range)
67 {
68     if (range.startsWith('[') && range.endsWith(']')) {
69         range.remove(0, 1).chop(1);
70     }
71     range.remove('$');
72 
73     const bool isPoint = !range.contains( ':' );
74     QRegExp regEx(isPoint ? "(|.*\\.|.*\\!)([A-Z0-9]+)"
75 		          : "(|.*\\.|.*\\!)([A-Z]+[0-9]+)\\:(|.*\\.|.*\\!)([A-Z0-9]+)");
76     if (regEx.indexIn(range) >= 0) {
77         range.clear();
78         QString sheetName = regEx.cap(1);
79         if (sheetName.endsWith(QLatin1Char('.')) || sheetName.endsWith(QLatin1Char('!')))
80             sheetName.chop(1);
81         if (!sheetName.isEmpty())
82             range = sheetName + '.';
83         range += regEx.cap(2);
84         if (!isPoint)
85             range += ':' + regEx.cap(4);
86     }
87 
88     return range;
89 }
90 
tintColor(const QColor & color,qreal tintfactor)91 QColor KoOdfChartWriter::tintColor(const QColor & color, qreal tintfactor)
92 {
93     QColor retColor;
94     const qreal  nonTindedPart = 1.0 - tintfactor;
95     qreal luminance = 0.0;
96     qreal sat = 0.0;
97     qreal hue = 0.0;
98     color.getHslF(&hue, &sat, &luminance);
99     luminance = luminance * tintfactor + nonTindedPart;
100     retColor.setHslF(hue, sat, luminance);
101 //     const int tintedColor = 255 * nonTindedPart;
102 //     retColor.setRed(tintedColor + tintfactor * color.red());
103 //     retColor.setGreen(tintedColor + tintfactor * color.green());
104 //     retColor.setBlue(tintedColor + tintfactor * color.blue());
105 
106     return retColor;
107 }
108 
calculateColorFromGradientStop(const KoChart::Gradient::GradientStop & grad)109 QColor KoOdfChartWriter::calculateColorFromGradientStop(const KoChart::Gradient::GradientStop& grad)
110 {
111     QColor color = grad.knownColorValue;
112 
113     const int tintedColor = 255 * grad.tintVal / 100.0;
114     const qreal  nonTindedPart = 1.0 - grad.tintVal / 100.0;
115     color.setRed(tintedColor + nonTindedPart * color.red());
116     color.setGreen(tintedColor + nonTindedPart * color.green());
117     color.setBlue(tintedColor + nonTindedPart * color.blue());
118 
119     return color;
120 }
121 
generateGradientStyle(KoGenStyles & mainStyles,const KoChart::Gradient * grad)122 QString KoOdfChartWriter::generateGradientStyle(KoGenStyles& mainStyles,
123 						const KoChart::Gradient* grad)
124 {
125     KoGenStyle gradStyle(KoGenStyle::GradientStyle);
126     gradStyle.addAttribute("draw:style", "linear");
127 
128     QColor startColor = calculateColorFromGradientStop(grad->gradientStops.first());
129     QColor endColor = calculateColorFromGradientStop(grad->gradientStops.last());
130 
131     gradStyle.addAttribute("draw:start-color", startColor.name());
132     gradStyle.addAttribute("draw:end-color", endColor.name());
133     gradStyle.addAttribute("draw:angle", QString::number(grad->angle));
134 
135     return mainStyles.insert(gradStyle, "ms_chart_gradient");
136 }
137 
labelFontColor() const138 QColor KoOdfChartWriter::labelFontColor() const
139 {
140     return QColor();
141 }
142 
genChartAreaStyle(KoGenStyle & style,KoGenStyles & styles,KoGenStyles & mainStyles)143 QString KoOdfChartWriter::genChartAreaStyle(KoGenStyle& style, KoGenStyles& styles,
144 					    KoGenStyles& mainStyles)
145 {
146     if (chart()->m_fillGradient) {
147         style.addProperty("draw:fill", "gradient", KoGenStyle::GraphicType);
148         style.addProperty("draw:fill-gradient-name",
149 			  generateGradientStyle(mainStyles, chart()->m_fillGradient),
150 			  KoGenStyle::GraphicType);
151     } else {
152 	style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
153 
154 	QColor color;
155 	if (chart()->m_areaFormat
156 	    && chart()->m_areaFormat->m_fill
157 	    && chart()->m_areaFormat->m_foreground.isValid())
158 	{
159 	    color = chart()->m_areaFormat->m_foreground;
160 	}
161 	else
162 	    color = QColor("#FFFFFF");
163 	style.addProperty("draw:fill-color", color.name(), KoGenStyle::GraphicType);
164 
165 	if (color.alpha() < 255)
166 	    style.addProperty("draw:opacity",
167 			      QString("%1%").arg(chart()->m_areaFormat->m_foreground.alphaF()
168 						 * 100.0),
169 			      KoGenStyle::GraphicType);
170     }
171 
172     return styles.insert(style, "ch");
173 }
174 
175 
genChartAreaStyle(KoGenStyles & styles,KoGenStyles & mainStyles)176 QString KoOdfChartWriter::genChartAreaStyle(KoGenStyles& styles, KoGenStyles& mainStyles)
177 {
178     KoGenStyle style(KoGenStyle::GraphicAutoStyle, "chart");
179 
180     return genChartAreaStyle(style, styles, mainStyles);
181 }
182 
183 
genPlotAreaStyle(KoGenStyle & style,KoGenStyles & styles,KoGenStyles & mainStyles)184 QString KoOdfChartWriter::genPlotAreaStyle(KoGenStyle& style, KoGenStyles& styles,
185 					   KoGenStyles& mainStyles)
186 {
187     KoChart::AreaFormat *areaFormat = ((chart()->m_plotArea
188 					&& chart()->m_plotArea->m_areaFormat
189 					&& chart()->m_plotArea->m_areaFormat->m_fill)
190 				       ? chart()->m_plotArea->m_areaFormat
191 				       : chart()->m_areaFormat);
192     if (chart()->m_plotAreaFillGradient) {
193         style.addProperty("draw:fill", "gradient", KoGenStyle::GraphicType);
194         style.addProperty("draw:fill-gradient-name",
195 			  generateGradientStyle(mainStyles, chart()->m_plotAreaFillGradient),
196 			  KoGenStyle::GraphicType);
197     } else {
198         style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
199 
200 	QColor color;
201 	if (areaFormat && areaFormat->m_foreground.isValid())
202 	    color = areaFormat->m_foreground;
203 	else
204 	    color = QColor(paletteIsSet ? "#C0C0C0" : "#FFFFFF");
205 	style.addProperty("draw:fill-color", color.name(), KoGenStyle::GraphicType);
206 
207 	if (color.alpha() < 255)
208 	    style.addProperty("draw:opacity",
209 			      QString("%1%").arg(areaFormat->m_foreground.alphaF() * 100.0),
210 			      KoGenStyle::GraphicType);
211     }
212 
213     return styles.insert(style, "ch");
214 }
215 
216 
addShapePropertyStyle(KoChart::Series * series,KoGenStyle & style,KoGenStyles &)217 void KoOdfChartWriter::addShapePropertyStyle(/*const*/ KoChart::Series* series, KoGenStyle& style,
218 					     KoGenStyles& /*mainStyles*/)
219 {
220     Q_ASSERT(series);
221     bool marker = false;
222     KoChart::ScatterImpl* impl = dynamic_cast< KoChart::ScatterImpl* >(m_chart->m_impl);
223 
224     if (impl)
225         marker = (impl->style == KoChart::ScatterImpl::Marker
226 		  || impl->style == KoChart::ScatterImpl::LineMarker);
227 
228     if (series->spPr->lineFill.valid) {
229         if (series->spPr->lineFill.type == KoChart::Fill::Solid) {
230             style.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);
231             style.addProperty("svg:stroke-color", series->spPr->lineFill.solidColor.name(),
232 			      KoGenStyle::GraphicType);
233         }
234         else if (series->spPr->lineFill.type == KoChart::Fill::None) {
235             style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
236 	}
237     }
238     else if (  (paletteIsSet && m_chart->m_impl->name() != "scatter")
239              || m_chart->m_showLines)
240     {
241         const int curSerNum = m_chart->m_series.indexOf(series);
242         style.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);
243         style.addProperty("svg:stroke-color", m_palette.at(24 + curSerNum).name(),
244 			  KoGenStyle::GraphicType);
245     }
246     else if (paletteIsSet && m_chart->m_impl->name() == "scatter")
247         style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
248     if (series->spPr->areaFill.valid) {
249         if (series->spPr->areaFill.type == KoChart::Fill::Solid) {
250             style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
251             style.addProperty("draw:fill-color", series->spPr->areaFill.solidColor.name(),
252 			      KoGenStyle::GraphicType);
253         }
254         else if (series->spPr->areaFill.type == KoChart::Fill::None)
255             style.addProperty("draw:fill", "none", KoGenStyle::GraphicType);
256     }
257     else if (paletteIsSet
258 	     && !(m_chart->m_markerType != KoChart::NoMarker || marker)
259 	     && series->m_markerType == KoChart::NoMarker)
260     {
261         const int curSerNum = m_chart->m_series.indexOf(series) % 8;
262         style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
263         style.addProperty("draw:fill-color", m_palette.at(16 + curSerNum).name(),
264 			  KoGenStyle::GraphicType);
265     }
266 }
267 
genPlotAreaStyle(KoGenStyles & styles,KoGenStyles & mainStyles)268 QString KoOdfChartWriter::genPlotAreaStyle(KoGenStyles& styles, KoGenStyles& mainStyles)
269 {
270     KoGenStyle style(KoGenStyle::ChartAutoStyle/*, "chart"*/);
271     return genPlotAreaStyle(style, styles, mainStyles);
272 }
273 
replaceSheet(const QString & originalString,const QString & replacementSheet)274 QString KoOdfChartWriter::replaceSheet(const QString &originalString,
275 				       const QString &replacementSheet)
276 {
277     QStringList split = originalString.split(QLatin1Char('!'));
278     split[0] = replacementSheet;
279     return split.join(QString::fromLatin1("!"));
280 }
281 
set2003ColorPalette(QList<QColor> palette)282 void KoOdfChartWriter::set2003ColorPalette(QList < QColor > palette)
283 {
284     m_palette = palette;
285     paletteIsSet = true;
286 }
287 
markerType(KoChart::MarkerType type,int currentSeriesNumber)288 QString KoOdfChartWriter::markerType(KoChart::MarkerType type, int currentSeriesNumber)
289 {
290     QString markerName;
291     switch(type) {
292         case NoMarker:
293             break;
294         case AutoMarker: { // auto marker type
295             const int resNum = currentSeriesNumber % 3;
296             if (resNum == 0)
297                 markerName = "square";
298             else if (resNum == 1)
299                 markerName = "diamond";
300             else if (resNum == 2)
301                 markerName = "circle";
302         } break;
303         case SquareMarker:
304             markerName = "square";
305             break;
306         case DiamondMarker:
307             markerName = "diamond";
308             break;
309         case StarMarker:
310             markerName = "star";
311             break;
312         case TriangleMarker:
313             markerName = "arrow-up";
314             break;
315         case DotMarker:
316             markerName = "dot";
317             break;
318         case PlusMarker:
319             markerName = "plus";
320             break;
321         case SymbolXMarker:
322             markerName = "x";
323             break;
324         case CircleMarker:
325             markerName = "circle";
326             break;
327         case DashMarker:
328             markerName = "horizontal-bar";
329             break;
330     }
331 
332     return markerName;
333 }
334 
335 
336 // ----------------------------------------------------------------
337 //                 The actual saving code
338 
339 
saveIndex(KoXmlWriter * xmlWriter)340 bool KoOdfChartWriter::saveIndex(KoXmlWriter* xmlWriter)
341 {
342     if (!chart() || m_href.isEmpty())
343         return false;
344 
345     // This because for presesentations the frame is done in read_graphicFrame
346     if (!m_drawLayer) {
347         xmlWriter->startElement("draw:frame");
348         // used in opendocumentpresentation for layers
349         //if (m_drawLayer)
350         //    xmlWriter->addAttribute("draw:layer", "layout");
351 
352         // used in opendocumentspreadsheet to reference cells
353         if (!m_endCellAddress.isEmpty()) {
354             xmlWriter->addAttribute("table:end-cell-address", m_endCellAddress);
355             xmlWriter->addAttributePt("table:end-x", m_end_x);
356             xmlWriter->addAttributePt("table:end-y", m_end_y);
357         }
358 
359         xmlWriter->addAttributePt("svg:x", m_x);
360         xmlWriter->addAttributePt("svg:y", m_y);
361         if (m_width > 0)
362             xmlWriter->addAttributePt("svg:width", m_width);
363         if (m_height > 0)
364             xmlWriter->addAttributePt("svg:height", m_height);
365     }
366     //xmlWriter->addAttribute("draw:z-index", "0");
367     xmlWriter->startElement("draw:object");
368     //TODO don't show on e.g. presenter
369     if (!m_notifyOnUpdateOfRanges.isEmpty())
370         xmlWriter->addAttribute("draw:notify-on-update-of-ranges", m_notifyOnUpdateOfRanges);
371 
372     xmlWriter->addAttribute("xlink:href", "./" + m_href);
373     xmlWriter->addAttribute("xlink:type", "simple");
374     xmlWriter->addAttribute("xlink:show", "embed");
375     xmlWriter->addAttribute("xlink:actuate", "onLoad");
376 
377     xmlWriter->endElement(); // draw:object
378     if (!m_drawLayer) {
379         xmlWriter->endElement(); // draw:frame
380     }
381     return true;
382 }
383 
saveContent(KoStore * store,KoXmlWriter * manifestWriter)384 bool KoOdfChartWriter::saveContent(KoStore* store, KoXmlWriter* manifestWriter)
385 {
386     if (!chart() || !chart()->m_impl || m_href.isEmpty())
387         return false;
388 
389     KoGenStyles styles;
390     KoGenStyles mainStyles;
391 
392     store->pushDirectory();
393     store->enterDirectory(m_href);
394 
395     KoOdfWriteStore s(store);
396     KoXmlWriter* bodyWriter = s.bodyWriter();
397     KoXmlWriter* contentWriter = s.contentWriter();
398     Q_ASSERT(bodyWriter && contentWriter);
399 
400     bodyWriter->startElement("office:body");
401     bodyWriter->startElement("office:chart");
402 
403     //<chart:chart chart:class="chart:circle"
404     //             svg:width="8cm" svg:height="7cm"
405     //             chart:style-name="ch1">
406     bodyWriter->startElement("chart:chart");
407 
408     if (!chart()->m_impl->name().isEmpty()) {
409         bodyWriter->addAttribute("chart:class", "chart:" + chart()->m_impl->name());
410     }
411 
412     if (m_width > 0) {
413         bodyWriter->addAttributePt("svg:width", m_width);
414     }
415     if (m_height > 0) {
416         bodyWriter->addAttributePt("svg:height", m_height);
417     }
418 
419     bodyWriter->addAttribute("chart:style-name", genChartAreaStyle(styles, mainStyles));
420 
421     // <chart:title svg:x="5.618cm" svg:y="0.14cm" chart:style-name="ch2">
422     //     <text:p>PIE CHART</text:p>
423     // </chart:title>
424     if (!chart()->m_title.isEmpty()) {
425         bodyWriter->startElement("chart:title");
426 
427         /* TODO we can't determine this because by default we need to center the title,
428         in order to center it we need to know the textbox size, and to do that we need
429         the used font metrics.
430 
431         Also, for now, the default implementation of KChart centers
432         the title, so we get close to the expected behavior. We ignore any offset though.
433 
434         Nonetheless, the formula should be something like this:
435         const int widht = m_width/2 - textWidth/2 + sprcToPt(t->m_x1, vertical);
436         const int height = m_height/2 - textHeight/2 + sprcToPt(t->m_y1, horizontal);
437         bodyWriter->addAttributePt("svg:x", width);
438         bodyWriter->addAttributePt("svg:y", height);
439         */
440 
441         // NOTE: Don't load width or height, the record MUST be ignored and
442         //       determined by the application
443         // see [MS-XLS] p. 362
444 
445         bodyWriter->startElement("text:p");
446         bodyWriter->addTextNode(chart()->m_title);
447         bodyWriter->endElement(); // text:p
448         bodyWriter->endElement(); // chart:title
449     }
450 
451     // Legend
452     if (chart()->m_legend) {
453         bodyWriter->startElement("chart:legend");
454         bodyWriter->addAttribute("chart:legend-position", "end");
455 
456         KoGenStyle legendstyle(KoGenStyle::ChartAutoStyle, "chart");
457 
458         QColor labelColor = labelFontColor();
459         if (labelColor.isValid())
460             legendstyle.addProperty("fo:font-color", labelColor.name(), KoGenStyle::TextType);
461 
462         bodyWriter->addAttribute("chart:style-name", styles.insert(legendstyle, "lg"));
463 
464         bodyWriter->endElement(); // chart:legend
465     }
466 
467     // <chart:plot-area chart:style-name="ch3"
468     //                  table:cell-range-address="Sheet1.C2:Sheet1.E2"
469     //                  svg:x="0.16cm" svg:y="0.14cm">
470     bodyWriter->startElement("chart:plot-area");
471 
472     if (chart()->m_is3d) {
473         //bodyWriter->addAttribute("dr3d:transform", "matrix (0.893670830886674 0.102940425033731 -0.436755898547686 -0.437131441492021 0.419523087196176 -0.795560483036015 0.101333848646097 0.901888933407692 0.419914042293545 0cm 0cm 0cm)");
474         //bodyWriter->addAttribute("dr3d:vrp", "(12684.722548717 7388.35827488833 17691.2795565958)");
475         //bodyWriter->addAttribute("dr3d:vpn", "(0.416199821709347 0.173649045905254 0.892537795986984)");
476         //bodyWriter->addAttribute("dr3d:vup", "(-0.0733876362771618 0.984807599917971 -0.157379306090273)");
477         //bodyWriter->addAttribute("dr3d:projection", "parallel");
478         //bodyWriter->addAttribute("dr3d:distance", "4.2cm");
479         //bodyWriter->addAttribute("dr3d:focal-length", "8cm");
480         //bodyWriter->addAttribute("dr3d:shadow-slant", "0");
481         //bodyWriter->addAttribute("dr3d:shade-mode", "flat");
482         //bodyWriter->addAttribute("dr3d:ambient-color", "#b3b3b3");
483         //bodyWriter->addAttribute("dr3d:lighting-mode", "true");
484     }
485 
486     KoGenStyle chartstyle(KoGenStyle::ChartAutoStyle, "chart");
487     //chartstyle.addProperty("chart:connect-bars", "false");
488     //chartstyle.addProperty("chart:include-hidden-cells", "false");
489     chartstyle.addProperty("chart:auto-position", "true");
490     chartstyle.addProperty("chart:auto-size", "true");
491     chartstyle.addProperty("chart:angle-offset", chart()->m_angleOffset);
492 
493     //chartstyle.addProperty("chart:series-source", "rows");
494     //chartstyle.addProperty("chart:sort-by-x-values", "false");
495     //chartstyle.addProperty("chart:right-angled-axes", "true");
496     if (chart()->m_is3d) {
497         chartstyle.addProperty("chart:three-dimensional", "true");
498     }
499     //chartstyle.addProperty("chart:angle-offset", "90");
500     //chartstyle.addProperty("chart:series-source", "rows");
501     //chartstyle.addProperty("chart:right-angled-axes", "false");
502     if (chart()->m_transpose) {
503         chartstyle.addProperty("chart:vertical", "true");
504     }
505     if (chart()->m_stacked) {
506         chartstyle.addProperty("chart:stacked", "true");
507     }
508     if (chart()->m_f100) {
509         chartstyle.addProperty("chart:percentage", "true");
510     }
511     bodyWriter->addAttribute("chart:style-name", genPlotAreaStyle(chartstyle, styles, mainStyles));
512 
513     QString verticalCellRangeAddress = chart()->m_verticalCellRangeAddress;
514 // FIXME microsoft treats the regions from this area in a different order, so don't use it or x and y values will be switched
515 //     if (!chart()->m_cellRangeAddress.isEmpty()) {
516 //         if (sheetReplacement)
517 //             bodyWriter->addAttribute("table:cell-range-address", replaceSheet(normalizeCellRange(m_cellRangeAddress), QString::fromLatin1("local"))); //"Sheet1.C2:Sheet1.E5");
518 //         else
519 //             bodyWriter->addAttribute("table:cell-range-address", normalizeCellRange(m_cellRangeAddress)); //"Sheet1.C2:Sheet1.E5");
520 //     }
521 
522     /*FIXME
523     if (verticalCellRangeAddress.isEmpty()) {
524         // only add the chart:data-source-has-labels if no chart:categories with a table:cell-range-address was defined within an axis.
525         bodyWriter->addAttribute("chart:data-source-has-labels", "both");
526     }
527     */
528 
529     //bodyWriter->addAttribute("svg:x", "0.16cm"); //FIXME
530     //bodyWriter->addAttribute("svg:y", "0.14cm"); //FIXME
531     //bodyWriter->addAttribute("svg:width", "6.712cm"); //FIXME
532     //bodyWriter->addAttribute("svg:height", "6.58cm"); //FIXME
533 
534     const bool definesCategories = chart()->m_impl->name() != "scatter"; // scatter charts are using domains
535     int countXAxis = 0;
536     int countYAxis = 0;
537     foreach (KoChart::Axis* axis, chart()->m_axes) {
538         //TODO handle series-axis
539         if (axis->m_type == KoChart::Axis::SeriesAxis) continue;
540 
541         bodyWriter->startElement("chart:axis");
542 
543         KoGenStyle axisstyle(KoGenStyle::ChartAutoStyle, "chart");
544 
545         if (axis->m_reversed)
546             axisstyle.addProperty("chart:reverse-direction", "true", KoGenStyle::ChartType);
547 
548         //FIXME this hits an infinite-looping bug in kdchart it seems... maybe fixed with a newer version
549 //         if (axis->m_logarithmic)
550 //             axisstyle.addProperty("chart:logarithmic", "true", KoGenStyle::ChartType);
551 
552         if (!axis->m_autoMinimum)
553             axisstyle.addProperty("chart:minimum", QString::number(axis->m_minimum, 'f'),
554 				  KoGenStyle::ChartType);
555         if (!axis->m_autoMaximum)
556             axisstyle.addProperty("chart:maximum", QString::number(axis->m_maximum, 'f'),
557 				  KoGenStyle::ChartType);
558 
559         axisstyle.addProperty("fo:font-size", QString("%0pt").arg(chart()->m_textSize),
560 			      KoGenStyle::TextType);
561 
562         QColor labelColor = labelFontColor();
563         if (labelColor.isValid())
564             axisstyle.addProperty("fo:font-color", labelColor.name(), KoGenStyle::TextType);
565 
566         if (!axis->m_numberFormat.isEmpty()) {
567             const KoGenStyle style = NumberFormatParser::parse(axis->m_numberFormat, &styles);
568             axisstyle.addAttribute("style:data-style-name", styles.insert(style, "ds"));
569         }
570 
571         bodyWriter->addAttribute("chart:style-name", styles.insert(axisstyle, "ch"));
572 
573         switch(axis->m_type) {
574             case KoChart::Axis::VerticalValueAxis:
575                 bodyWriter->addAttribute("chart:dimension", "y");
576                 bodyWriter->addAttribute("chart:name", QString("y%1").arg(++countYAxis));
577                 break;
578             case KoChart::Axis::HorizontalValueAxis:
579                 bodyWriter->addAttribute("chart:dimension", "x");
580                 bodyWriter->addAttribute("chart:name", QString("x%1").arg(++countXAxis));
581                 if (countXAxis == 1 && definesCategories && !verticalCellRangeAddress.isEmpty()) {
582                     bodyWriter->startElement("chart:categories");
583                     if (sheetReplacement)
584                         verticalCellRangeAddress
585 			    = normalizeCellRange(replaceSheet(verticalCellRangeAddress,
586 							      QString::fromLatin1("local")));
587                     else
588                         verticalCellRangeAddress = normalizeCellRange(verticalCellRangeAddress);
589                     bodyWriter->addAttribute("table:cell-range-address", verticalCellRangeAddress); //"Sheet1.C2:Sheet1.E2");
590                     bodyWriter->endElement();
591                 }
592                 break;
593             default: break;
594         }
595 
596         if (axis->m_majorGridlines.m_format.m_style != KoChart::LineFormat::None) {
597             bodyWriter->startElement("chart:grid");
598             bodyWriter->addAttribute("chart:class", "major");
599             bodyWriter->endElement(); // chart:grid
600         }
601 
602         if (axis->m_minorGridlines.m_format.m_style != KoChart::LineFormat::None) {
603             bodyWriter->startElement("chart:grid");
604             bodyWriter->addAttribute("chart:class", "minor");
605             bodyWriter->endElement(); // chart:grid
606         }
607         bodyWriter->endElement(); // chart:axis
608     }
609 
610     // Add at least one x-axis.
611     if (countXAxis == 0) {
612         bodyWriter->startElement("chart:axis");
613         bodyWriter->addAttribute("chart:dimension", "x");
614         bodyWriter->addAttribute("chart:name", "primary-x");
615 
616         if (definesCategories && !verticalCellRangeAddress.isEmpty()) {
617             bodyWriter->startElement("chart:categories");
618             if (sheetReplacement)
619                 verticalCellRangeAddress
620 		    = normalizeCellRange(replaceSheet(verticalCellRangeAddress,
621 						      QString::fromLatin1("local")));
622             else
623                 verticalCellRangeAddress = normalizeCellRange(verticalCellRangeAddress);
624 
625             bodyWriter->addAttribute("table:cell-range-address", verticalCellRangeAddress);
626             bodyWriter->endElement();
627         }
628 
629         bodyWriter->endElement(); // chart:axis
630     }
631 
632     // Add at least one y-axis.
633     if (countYAxis == 0) {
634         bodyWriter->startElement("chart:axis");
635         bodyWriter->addAttribute("chart:dimension", "y");
636         bodyWriter->addAttribute("chart:name", "primary-y");
637         bodyWriter->endElement(); // chart:axis
638     }
639 
640     //<chart:axis chart:dimension="x" chart:name="primary-x" chart:style-name="ch4"/>
641     //<chart:axis chart:dimension="y" chart:name="primary-y" chart:style-name="ch5"><chart:grid chart:style-name="ch6" chart:class="major"/></chart:axis>
642 
643     // NOTE: The XLS format specifies that if an explodeFactor that is > 100
644     //       is found, we should find the biggest and make it 100, then scale
645     //       all the other factors accordingly.
646     // see 2.4.195 PieFormat
647     int maxExplode = 100;
648     foreach (KoChart::Series* series, chart()->m_series) {
649         foreach (KoChart::Format* f, series->m_datasetFormat) {
650             if (KoChart::PieFormat* pieformat = dynamic_cast<KoChart::PieFormat*>(f)) {
651                 if (pieformat->m_pcExplode > 0) {
652                     maxExplode = qMax(maxExplode, pieformat->m_pcExplode);
653                 }
654             }
655         }
656     }
657 
658     // Area diagrams are special in that Excel displays the areas in another
659     // order than OpenOffice.org and Calligra Sheets. To make sure the same areas are
660     // visible we do the same as OpenOffice.org does and reverse the order.
661     if (chart()->m_impl->name() == "area") {
662         for (int i = chart()->m_series.count() - 1; i >= 0; --i) {
663             chart()->m_series.append(chart()->m_series.takeAt(i));
664         }
665     }
666 
667     // Save the series.
668     if (!saveSeries(styles, mainStyles, bodyWriter, maxExplode))
669 	return false;
670 
671     bodyWriter->startElement("chart:wall");
672     bodyWriter->endElement(); // chart:wall
673 
674     bodyWriter->startElement("chart:floor");
675     bodyWriter->endElement(); // chart:floor
676 
677     bodyWriter->endElement(); // chart:plot-area
678 
679     writeInternalTable(bodyWriter);
680 
681     bodyWriter->endElement(); // chart:chart
682     bodyWriter->endElement(); // office:chart
683     bodyWriter->endElement(); // office:body
684 
685 #ifdef CONTENTXML_DEBUG
686     debugOdf2 << bodyWriter->toString();
687 #endif
688 
689     styles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
690     s.closeContentWriter();
691 
692     if (store->open("styles.xml")) {
693         KoStoreDevice dev(store);
694         KoXmlWriter* stylesWriter = new KoXmlWriter(&dev);
695 
696         stylesWriter->startDocument("office:document-styles");
697         stylesWriter->startElement("office:document-styles");
698         stylesWriter->addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
699         stylesWriter->addAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
700         stylesWriter->addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
701         stylesWriter->addAttribute("xmlns:table", "urn:oasis:names:tc:opendocument:xmlns:table:1.0");
702         stylesWriter->addAttribute("xmlns:draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
703         stylesWriter->addAttribute("xmlns:fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
704         stylesWriter->addAttribute("xmlns:svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
705         stylesWriter->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
706         stylesWriter->addAttribute("xmlns:chart", "urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
707         stylesWriter->addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
708         stylesWriter->addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
709         stylesWriter->addAttribute("xmlns:number", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
710         stylesWriter->addAttribute("xmlns:dr3d", "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
711         stylesWriter->addAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
712         stylesWriter->addAttribute("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
713         stylesWriter->addAttribute("office:version", "1.2");
714         mainStyles.saveOdfStyles(KoGenStyles::MasterStyles, stylesWriter);
715         mainStyles.saveOdfStyles(KoGenStyles::DocumentStyles, stylesWriter); // office:style
716         mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, stylesWriter); // office:automatic-styles
717         stylesWriter->endElement();  // office:document-styles
718         stylesWriter->endDocument();
719 
720         delete stylesWriter;
721         store->close();
722     }
723 
724     manifestWriter->addManifestEntry(m_href + QLatin1Char('/'), "application/vnd.oasis.opendocument.chart");
725     manifestWriter->addManifestEntry(QString("%1/styles.xml").arg(m_href), "text/xml");
726     manifestWriter->addManifestEntry(QString("%1/content.xml").arg(m_href), "text/xml");
727 
728     store->popDirectory();
729     return true;
730 }
731 
732 // FIXME: We should probably create a KoOdfChartWriterContext out of these
733 //        parameters when we add more similar saving functions later.
saveSeries(KoGenStyles & styles,KoGenStyles & mainStyles,KoXmlWriter * bodyWriter,int maxExplode)734 bool KoOdfChartWriter::saveSeries(KoGenStyles &styles, KoGenStyles &mainStyles,
735 				  KoXmlWriter* bodyWriter, int maxExplode)
736 {
737     int curSerNum = 0;
738     bool lines = true;
739     bool marker = false;
740     Q_FOREACH (KoChart::Series* series, chart()->m_series) {
741         lines = true;
742         if (chart()->m_impl->name() == "scatter" && !paletteIsSet) {
743             KoChart::ScatterImpl* impl = static_cast< KoChart::ScatterImpl* >(chart()->m_impl);
744             lines = (impl->style == KoChart::ScatterImpl::Line
745 		     || impl->style == KoChart::ScatterImpl::LineMarker);
746             marker = (impl->style == KoChart::ScatterImpl::Marker
747 		      || impl->style == KoChart::ScatterImpl::LineMarker);
748         }
749         const bool noLineFill = ((series->spPr != 0)
750 				 && series->spPr->lineFill.type == KoChart::Fill::None);
751         lines = lines && !noLineFill;
752         lines = lines || m_chart->m_showLines;
753 
754 	// <chart:series chart:style-name="ch7"
755 	//               chart:values-cell-range-address="Sheet1.C2:Sheet1.E2"
756 	//               chart:class="chart:circle">
757         bodyWriter->startElement("chart:series");
758         KoGenStyle seriesstyle(KoGenStyle::GraphicAutoStyle, "chart");
759         if (series->spPr)
760             addShapePropertyStyle(series, seriesstyle, mainStyles);
761         else if (lines && paletteIsSet) {
762             lines = false;
763             seriesstyle.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);
764             seriesstyle.addProperty("svg:stroke-color", m_palette.at(24 + curSerNum).name(),
765 				    KoGenStyle::GraphicType);
766         }
767 
768         if (paletteIsSet
769 	    && m_chart->m_impl->name() != "ring"
770 	    && m_chart->m_impl->name() != "circle")
771 	{
772             if (series->m_markerType == KoChart::NoMarker
773 		&& m_chart->m_markerType == KoChart::NoMarker
774 		&& !marker)
775             {
776 		seriesstyle.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
777 		seriesstyle.addProperty("draw:fill-color", m_palette.at(16 + curSerNum).name(),
778 					KoGenStyle::GraphicType);
779             }
780         }
781 
782         if (series->m_markerType != KoChart::NoMarker) {
783             QString markerName = markerType(series->m_markerType, curSerNum);
784             if (!markerName.isEmpty()) {
785                 seriesstyle.addProperty("chart:symbol-type", "named-symbol", KoGenStyle::ChartType);
786                 seriesstyle.addProperty("chart:symbol-name", markerName, KoGenStyle::ChartType);
787             }
788         }
789         else if (m_chart->m_markerType != KoChart::NoMarker || marker) {
790             QString markerName = markerType(m_chart->m_markerType == KoChart::NoMarker
791 					    ? KoChart::AutoMarker
792 					    : m_chart->m_markerType, curSerNum);
793             if (!markerName.isEmpty()) {
794                 seriesstyle.addProperty("chart:symbol-type", "named-symbol", KoGenStyle::ChartType);
795                 seriesstyle.addProperty("chart:symbol-name", markerName, KoGenStyle::ChartType);
796             }
797         }
798 
799         if (chart()->m_impl->name() != "circle" && chart()->m_impl->name() != "ring")
800             addDataThemeToStyle(seriesstyle, curSerNum, chart()->m_series.count(), lines);
801         //seriesstyle.addProperty("draw:stroke", "solid");
802         //seriesstyle.addProperty("draw:fill-color", "#ff0000");
803 
804         foreach (KoChart::Format* f, series->m_datasetFormat) {
805             if (KoChart::PieFormat* pieformat = dynamic_cast<KoChart::PieFormat*>(f)) {
806                 if (pieformat->m_pcExplode > 0) {
807                     // Note that 100.0/maxExplode will yield 1.0 most of the
808                     // time, that's why do that division first
809                     const int pcExplode = (int)((float)pieformat->m_pcExplode * (100.0 / (float)maxExplode));
810                     seriesstyle.addProperty("chart:pie-offset", pcExplode, KoGenStyle::ChartType);
811                 }
812             }
813         }
814 
815         if (series->m_showDataLabelValues && series->m_showDataLabelPercent) {
816             seriesstyle.addProperty("chart:data-label-number", "value-and-percentage",
817 				    KoGenStyle::ChartType);
818         } else if (series->m_showDataLabelValues) {
819             seriesstyle.addProperty("chart:data-label-number", "value", KoGenStyle::ChartType);
820         } else if (series->m_showDataLabelPercent) {
821             seriesstyle.addProperty("chart:data-label-number", "percentage", KoGenStyle::ChartType);
822         }
823 
824         if (series->m_showDataLabelCategory) {
825             seriesstyle.addProperty("chart:data-label-text", "true", KoGenStyle::ChartType);
826         }
827         //seriesstyle.addProperty("chart:data-label-symbol", "true", KoGenStyle::ChartType);
828 
829         if (!series->m_numberFormat.isEmpty()) {
830             const KoGenStyle style = NumberFormatParser::parse(series->m_numberFormat, &styles);
831             seriesstyle.addAttribute("style:data-style-name", styles.insert(style, "ds"));
832         }
833 
834         bodyWriter->addAttribute("chart:style-name", styles.insert(seriesstyle, "ch"));
835 
836         // ODF does not support custom labels so we depend on the
837         // SeriesLegendOrTrendlineName being defined and to point to a valid
838         // cell to be able to display custom labels.
839         if (series->m_datasetValue.contains(KoChart::Value::SeriesLegendOrTrendlineName)) {
840             KoChart::Value* v = series->m_datasetValue[KoChart::Value::SeriesLegendOrTrendlineName];
841             if (!v->m_formula.isEmpty()) {
842                 bodyWriter->addAttribute("chart:label-cell-address",
843 					 (v->m_type == KoChart::Value::CellRange
844 					  ? normalizeCellRange(v->m_formula)
845 					  : v->m_formula));
846             }
847         }
848 
849         if (!series->m_labelCell.isEmpty()) {
850             QString labelAddress = series->m_labelCell;
851             if (sheetReplacement)
852                 labelAddress = normalizeCellRange(replaceSheet(labelAddress,
853 							       QString::fromLatin1("local")));
854             else
855                 labelAddress = normalizeCellRange(labelAddress);
856             bodyWriter->addAttribute("chart:label-cell-address", labelAddress);
857         }
858 
859         QString valuesCellRangeAddress;
860         if (sheetReplacement)
861             valuesCellRangeAddress
862 		= normalizeCellRange(replaceSheet(series->m_valuesCellRangeAddress,
863 						  QString::fromLatin1("local")));
864         else
865             valuesCellRangeAddress = normalizeCellRange(series->m_valuesCellRangeAddress);
866 
867         if (!valuesCellRangeAddress.isEmpty()) {
868 	    // "Sheet1.C2:Sheet1.E2";
869             bodyWriter->addAttribute("chart:values-cell-range-address", valuesCellRangeAddress);
870         }
871         else if (!series->m_domainValuesCellRangeAddress.isEmpty()) {
872 	    // "Sheet1.C2:Sheet1.E2";
873             bodyWriter->addAttribute("chart:values-cell-range-address",
874 				     series->m_domainValuesCellRangeAddress.last());
875         }
876 
877         bodyWriter->addAttribute("chart:class", "chart:" + chart()->m_impl->name());
878 
879 //         if (chart()->m_impl->name() == "scatter") {
880 //             bodyWriter->startElement("chart:domain");
881 //             bodyWriter->addAttribute("table:cell-range-address", verticalCellRangeAddress); //"Sheet1.C2:Sheet1.E5");
882 //             bodyWriter->endElement();
883 //         } else if (chart()->m_impl->name() == "bubble") {
884 
885             QString domainRange;
886             Q_FOREACH (const QString& curRange, series->m_domainValuesCellRangeAddress) {
887                 bodyWriter->startElement("chart:domain");
888                 if (sheetReplacement)
889                     domainRange = normalizeCellRange(replaceSheet(curRange, QString::fromLatin1("local")));
890                 else
891                     domainRange = normalizeCellRange(curRange);
892                 if (!domainRange.isEmpty())
893                     bodyWriter->addAttribute("table:cell-range-address", domainRange);
894                 bodyWriter->endElement();
895             }
896 //             if (series->m_domainValuesCellRangeAddress.count() == 1){
897 //                 bodyWriter->startElement("chart:domain");
898 //                 bodyWriter->addAttribute("table:cell-range-address", series->m_domainValuesCellRangeAddress.last()); //"Sheet1.C2:Sheet1.E5");
899 //                 bodyWriter->endElement();
900 //             }
901 //             if (series->m_domainValuesCellRangeAddress.isEmpty()){
902 //                 bodyWriter->startElement("chart:domain");
903 //                 bodyWriter->addAttribute("table:cell-range-address", series->m_valuesCellRangeAddress); //"Sheet1.C2:Sheet1.E5");
904 //                 bodyWriter->endElement();
905 //                 bodyWriter->startElement("chart:domain");
906 //                 bodyWriter->addAttribute("table:cell-range-address", series->m_valuesCellRangeAddress); //"Sheet1.C2:Sheet1.E5");
907 //                 bodyWriter->endElement();
908 //             }
909 //         }
910 
911         for (int j = 0; j < series->m_countYValues; ++j) {
912             bodyWriter->startElement("chart:data-point");
913             KoGenStyle gs(KoGenStyle::GraphicAutoStyle, "chart");
914 
915             if (chart()->m_impl->name() == "circle" || chart()->m_impl->name() == "ring") {
916                 QColor fillColor;
917                 if (j < series->m_dataPoints.count()) {
918                     KoChart::DataPoint *dataPoint = series->m_dataPoints[j];
919                     if (dataPoint->m_areaFormat) {
920                         fillColor = dataPoint->m_areaFormat->m_foreground;
921                     }
922                 }
923 
924                 if (fillColor.isValid()) {
925                     gs.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
926                     gs.addProperty("draw:fill-color", fillColor.name(), KoGenStyle::GraphicType);
927                 }
928                 else if (series->m_markerType == KoChart::NoMarker
929 			 && m_chart->m_markerType == KoChart::NoMarker
930 			 && !marker)
931 		{
932                     if (paletteIsSet) {
933                         gs.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
934                         gs.addProperty("draw:fill-color", m_palette.at(16 + j).name(),
935 				       KoGenStyle::GraphicType);
936                     }
937                     else {
938                         addDataThemeToStyle(gs, j, series->m_countYValues, lines);
939                     }
940                 }
941             }/*
942             else
943             {
944                 addSeriesThemeToStyle(gs, curSerNum, chart()->m_series.count());
945             }*/
946 
947             //gs.addProperty("chart:solid-type", "cuboid", KoGenStyle::ChartType);
948             //gs.addProperty("draw:fill-color",j==0?"#004586":j==1?"#ff420e":"#ffd320",
949 	    //               KoGenStyle::GraphicType);
950             bodyWriter->addAttribute("chart:style-name", styles.insert(gs, "ch"));
951 
952             Q_FOREACH (KoChart::Text* t, series->m_texts) {
953                 bodyWriter->startElement("chart:data-label");
954                 bodyWriter->startElement("text:p");
955                 bodyWriter->addTextNode(t->m_text);
956                 bodyWriter->endElement();
957                 bodyWriter->endElement();
958             }
959 
960             bodyWriter->endElement();
961         }
962 
963         ++curSerNum;
964         bodyWriter->endElement(); // chart:series
965     }
966 
967     return true;
968 }
969 
970 
971 // ----------------------------------------------------------------
972 //                   Some helper functions
973 
974 
975 // Calculate fade factor as suggested in msoo xml reference page 4161
calculateFade(int index,int maxIndex)976 qreal KoOdfChartWriter::calculateFade(int index, int maxIndex)
977 {
978     return -70.0 + 140.0 * ((double) index / ((double) maxIndex + 1.0));
979 }
980 
shadeColor(const QColor & col,qreal factor)981 QColor KoOdfChartWriter::shadeColor(const QColor& col, qreal factor)
982 {
983     QColor result = col;
984     qreal luminance = 0.0;
985     qreal hue = 0.0;
986     qreal sat = 0.0;
987     result.getHslF(&hue, &sat, &luminance);
988     luminance *= factor;
989     result.setHslF(hue, sat, luminance);
990     return result;
991 }
992 
addDataThemeToStyle(KoGenStyle & style,int dataNumber,int maxNumData,bool strokes)993 void KoOdfChartWriter::addDataThemeToStyle(KoGenStyle& style, int dataNumber, int maxNumData,
994 					   bool strokes)
995 {
996     // FIXME: This is only relevant to themes, so remove this function after
997     //        we are done with saveContent().
998     Q_UNUSED(style);
999     Q_UNUSED(dataNumber);
1000     Q_UNUSED(maxNumData);
1001     Q_UNUSED(strokes);
1002 }
1003 
1004 
sprcToPt(int sprc,Orientation orientation)1005 float KoOdfChartWriter::sprcToPt(int sprc, Orientation orientation )
1006 {
1007     if (orientation & vertical)
1008         return (float)sprc * ( (float)m_width / 4000.0);
1009 
1010     return (float)sprc * ( (float)m_height / 4000.0);
1011 }
1012 
writeInternalTable(KoXmlWriter * bodyWriter)1013 void KoOdfChartWriter::writeInternalTable(KoXmlWriter* bodyWriter)
1014 {
1015     Q_ASSERT( bodyWriter );
1016     bodyWriter->startElement("table:table");
1017         bodyWriter->addAttribute( "table:name", "local" );
1018 
1019         bodyWriter->startElement( "table:table-header-columns" );
1020             bodyWriter->startElement( "table:table-column" );
1021             bodyWriter->endElement();
1022         bodyWriter->endElement();
1023 
1024         bodyWriter->startElement( "table:table-columns" );
1025             bodyWriter->startElement( "table:table-column" );
1026             bodyWriter->endElement();
1027         bodyWriter->endElement();
1028 
1029         bodyWriter->startElement( "table:table-rows" );
1030 
1031         const int rowCount = chart()->m_internalTable.maxRow();
1032         for (int r = 1; r <= rowCount; ++r) {
1033             bodyWriter->startElement("table:table-row");
1034             const int columnCount = chart()->m_internalTable.maxCellsInRow(r);
1035             for (int c = 1; c <= columnCount; ++c) {
1036                 bodyWriter->startElement("table:table-cell");
1037                 if (Cell* cell = chart()->m_internalTable.cell(c, r, false)) {
1038                     //debugOdf2 << "cell->m_value " << cell->m_value;
1039                     if (!cell->m_value.isEmpty()) {
1040                         if (!cell->m_valueType.isEmpty()) {
1041                             bodyWriter->addAttribute("office:value-type", cell->m_valueType);
1042                             if (cell->m_valueType == "string") {
1043                                 bodyWriter->addAttribute("office:string-value", cell->m_value);
1044                             } else if (cell->m_valueType == "boolean") {
1045                                 bodyWriter->addAttribute("office:boolean-value", cell->m_value);
1046                             } else if (cell->m_valueType == "date") {
1047                                 bodyWriter->addAttribute("office:date-value", cell->m_value);
1048                             } else if (cell->m_valueType == "time") {
1049                                 bodyWriter->addAttribute("office:time-value", cell->m_value);
1050                             } else { // float, percentage and currency including fraction and scientific
1051                                 bodyWriter->addAttribute("office:value", cell->m_value);
1052                             }
1053                         }
1054 
1055                         bodyWriter->startElement("text:p");
1056                         bodyWriter->addTextNode( cell->m_value );
1057                         bodyWriter->endElement(); // text:p
1058                     }
1059                 }
1060                 bodyWriter->endElement(); // table:table-cell
1061             }
1062             bodyWriter->endElement(); // table:table-row
1063         }
1064         bodyWriter->endElement(); // table:table-rows
1065     bodyWriter->endElement(); // table:table
1066 }
1067 
setSheetReplacement(bool val)1068 void KoOdfChartWriter::setSheetReplacement( bool val )
1069 {
1070     sheetReplacement = val;
1071 }
1072