Warning, file /office/calligra/filters/libodf2/chart/KoOdfChartWriter.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Sebastian Sauer <sebsauer@kdab.com>
0003  *  SPDX-FileCopyrightText: 2010 Carlos Licea <carlos@kdab.com>
0004  *  SPDX-FileCopyrightText: 2014 Inge Wallin <inge@lysator.liu.se>
0005  *
0006  *  SPDX-License-Identifier: LGPL-2.1-or-later
0007  */
0008 
0009 // Own
0010 #include "KoOdfChartWriter.h"
0011 
0012 // libstdc++
0013 #include <algorithm> // For std:find()
0014 
0015 // Calligra
0016 #include <KoStore.h>
0017 #include <KoXmlWriter.h>
0018 #include <KoOdfWriteStore.h>
0019 #include <KoStoreDevice.h>
0020 #include <KoGenStyles.h>
0021 #include <KoGenStyle.h>
0022 
0023 #include "Odf2Debug.h"
0024 #include <Charting.h>
0025 #include "NumberFormatParser.h"
0026 
0027 
0028 // Print the content of generated content.xml to the console for debugging purpose
0029 //#define CONTENTXML_DEBUG
0030 
0031 using namespace KoChart;
0032 
0033 KoOdfChartWriter::KoOdfChartWriter(KoChart::Chart* chart)
0034     : m_x(0)
0035     , m_y(0)
0036     , m_width(0)
0037     , m_height(0)
0038     , m_end_x(0)
0039     , m_end_y(0)
0040     , m_chart(chart)
0041     , sheetReplacement(true)
0042     , paletteIsSet(false)
0043 {
0044     Q_ASSERT(m_chart);
0045     m_drawLayer = false;
0046 }
0047 
0048 KoOdfChartWriter::~KoOdfChartWriter()
0049 {
0050 }
0051 
0052 
0053 // Takes a Excel cellrange and translates it into a ODF cellrange
0054 QString KoOdfChartWriter::normalizeCellRange(QString range)
0055 {
0056     if (range.startsWith('[') && range.endsWith(']')) {
0057         range.remove(0, 1).chop(1);
0058     }
0059     range.remove('$');
0060 
0061     const bool isPoint = !range.contains( ':' );
0062     QRegExp regEx(isPoint ? "(|.*\\.|.*\\!)([A-Z0-9]+)"
0063                   : "(|.*\\.|.*\\!)([A-Z]+[0-9]+)\\:(|.*\\.|.*\\!)([A-Z0-9]+)");
0064     if (regEx.indexIn(range) >= 0) {
0065         range.clear();
0066         QString sheetName = regEx.cap(1);
0067         if (sheetName.endsWith(QLatin1Char('.')) || sheetName.endsWith(QLatin1Char('!')))
0068             sheetName.chop(1);
0069         if (!sheetName.isEmpty())
0070             range = sheetName + '.';
0071         range += regEx.cap(2);
0072         if (!isPoint)
0073             range += ':' + regEx.cap(4);
0074     }
0075 
0076     return range;
0077 }
0078 
0079 QColor KoOdfChartWriter::tintColor(const QColor & color, qreal tintfactor)
0080 {
0081     QColor retColor;
0082     const qreal  nonTindedPart = 1.0 - tintfactor;
0083     qreal luminance = 0.0;
0084     qreal sat = 0.0;
0085     qreal hue = 0.0;
0086     color.getHslF(&hue, &sat, &luminance);
0087     luminance = luminance * tintfactor + nonTindedPart;
0088     retColor.setHslF(hue, sat, luminance);
0089 //     const int tintedColor = 255 * nonTindedPart;
0090 //     retColor.setRed(tintedColor + tintfactor * color.red());
0091 //     retColor.setGreen(tintedColor + tintfactor * color.green());
0092 //     retColor.setBlue(tintedColor + tintfactor * color.blue());
0093 
0094     return retColor;
0095 }
0096 
0097 QColor KoOdfChartWriter::calculateColorFromGradientStop(const KoChart::Gradient::GradientStop& grad)
0098 {
0099     QColor color = grad.knownColorValue;
0100 
0101     const int tintedColor = 255 * grad.tintVal / 100.0;
0102     const qreal  nonTindedPart = 1.0 - grad.tintVal / 100.0;
0103     color.setRed(tintedColor + nonTindedPart * color.red());
0104     color.setGreen(tintedColor + nonTindedPart * color.green());
0105     color.setBlue(tintedColor + nonTindedPart * color.blue());
0106 
0107     return color;
0108 }
0109 
0110 QString KoOdfChartWriter::generateGradientStyle(KoGenStyles& mainStyles,
0111                         const KoChart::Gradient* grad)
0112 {
0113     KoGenStyle gradStyle(KoGenStyle::GradientStyle);
0114     gradStyle.addAttribute("draw:style", "linear");
0115 
0116     QColor startColor = calculateColorFromGradientStop(grad->gradientStops.first());
0117     QColor endColor = calculateColorFromGradientStop(grad->gradientStops.last());
0118     
0119     gradStyle.addAttribute("draw:start-color", startColor.name());
0120     gradStyle.addAttribute("draw:end-color", endColor.name());
0121     gradStyle.addAttribute("draw:angle", QString::number(grad->angle));
0122 
0123     return mainStyles.insert(gradStyle, "ms_chart_gradient");
0124 }
0125 
0126 QColor KoOdfChartWriter::labelFontColor() const
0127 {
0128     return QColor();
0129 }
0130 
0131 QString KoOdfChartWriter::genChartAreaStyle(KoGenStyle& style, KoGenStyles& styles,
0132                         KoGenStyles& mainStyles)
0133 {
0134     if (chart()->m_fillGradient) {
0135         style.addProperty("draw:fill", "gradient", KoGenStyle::GraphicType);
0136         style.addProperty("draw:fill-gradient-name",
0137               generateGradientStyle(mainStyles, chart()->m_fillGradient),
0138               KoGenStyle::GraphicType);
0139     } else {
0140     style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
0141 
0142     QColor color;
0143     if (chart()->m_areaFormat
0144         && chart()->m_areaFormat->m_fill
0145         && chart()->m_areaFormat->m_foreground.isValid())
0146     {
0147         color = chart()->m_areaFormat->m_foreground;
0148     }
0149     else
0150         color = QColor(255, 255, 255);
0151     style.addProperty("draw:fill-color", color.name(), KoGenStyle::GraphicType);
0152 
0153     if (color.alpha() < 255)
0154         style.addProperty("draw:opacity",
0155                   QString("%1%").arg(chart()->m_areaFormat->m_foreground.alphaF()
0156                          * 100.0),
0157                   KoGenStyle::GraphicType);
0158     }
0159 
0160     return styles.insert(style, "ch");
0161 }
0162 
0163 
0164 QString KoOdfChartWriter::genChartAreaStyle(KoGenStyles& styles, KoGenStyles& mainStyles)
0165 {
0166     KoGenStyle style(KoGenStyle::GraphicAutoStyle, "chart");
0167 
0168     return genChartAreaStyle(style, styles, mainStyles);
0169 }
0170 
0171 
0172 QString KoOdfChartWriter::genPlotAreaStyle(KoGenStyle& style, KoGenStyles& styles,
0173                        KoGenStyles& mainStyles)
0174 {
0175     KoChart::AreaFormat *areaFormat = ((chart()->m_plotArea
0176                     && chart()->m_plotArea->m_areaFormat
0177                     && chart()->m_plotArea->m_areaFormat->m_fill)
0178                        ? chart()->m_plotArea->m_areaFormat
0179                        : chart()->m_areaFormat);
0180     if (chart()->m_plotAreaFillGradient) {
0181         style.addProperty("draw:fill", "gradient", KoGenStyle::GraphicType);
0182         style.addProperty("draw:fill-gradient-name",
0183               generateGradientStyle(mainStyles, chart()->m_plotAreaFillGradient),
0184               KoGenStyle::GraphicType);
0185     } else {
0186         style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
0187 
0188     QColor color;
0189     if (areaFormat && areaFormat->m_foreground.isValid())
0190         color = areaFormat->m_foreground;
0191     else
0192         color = QColor(paletteIsSet ? "#C0C0C0" : "#FFFFFF");
0193     style.addProperty("draw:fill-color", color.name(), KoGenStyle::GraphicType);
0194 
0195     if (color.alpha() < 255)
0196         style.addProperty("draw:opacity",
0197                   QString("%1%").arg(areaFormat->m_foreground.alphaF() * 100.0),
0198                   KoGenStyle::GraphicType);
0199     }
0200 
0201     return styles.insert(style, "ch");
0202 }
0203 
0204 
0205 void KoOdfChartWriter::addShapePropertyStyle(/*const*/ KoChart::Series* series, KoGenStyle& style,
0206                          KoGenStyles& /*mainStyles*/)
0207 {
0208     Q_ASSERT(series);
0209     bool marker = false;
0210     KoChart::ScatterImpl* impl = dynamic_cast< KoChart::ScatterImpl* >(m_chart->m_impl);
0211 
0212     if (impl)
0213         marker = (impl->style == KoChart::ScatterImpl::Marker 
0214           || impl->style == KoChart::ScatterImpl::LineMarker);
0215 
0216     if (series->spPr->lineFill.valid) {
0217         if (series->spPr->lineFill.type == KoChart::Fill::Solid) {
0218             style.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);
0219             style.addProperty("svg:stroke-color", series->spPr->lineFill.solidColor.name(),
0220                   KoGenStyle::GraphicType);
0221         }
0222         else if (series->spPr->lineFill.type == KoChart::Fill::None) {
0223             style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
0224     }
0225     }
0226     else if (  (paletteIsSet && m_chart->m_impl->name() != "scatter")
0227              || m_chart->m_showLines)
0228     {
0229         const int curSerNum = m_chart->m_series.indexOf(series);
0230         style.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);
0231         style.addProperty("svg:stroke-color", m_palette.at(24 + curSerNum).name(),
0232               KoGenStyle::GraphicType);
0233     }
0234     else if (paletteIsSet && m_chart->m_impl->name() == "scatter")
0235         style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
0236     if (series->spPr->areaFill.valid) {
0237         if (series->spPr->areaFill.type == KoChart::Fill::Solid) {
0238             style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
0239             style.addProperty("draw:fill-color", series->spPr->areaFill.solidColor.name(),
0240                   KoGenStyle::GraphicType);
0241         }
0242         else if (series->spPr->areaFill.type == KoChart::Fill::None)
0243             style.addProperty("draw:fill", "none", KoGenStyle::GraphicType);
0244     }
0245     else if (paletteIsSet
0246          && !(m_chart->m_markerType != KoChart::NoMarker || marker)
0247          && series->m_markerType == KoChart::NoMarker)
0248     {
0249         const int curSerNum = m_chart->m_series.indexOf(series) % 8;
0250         style.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
0251         style.addProperty("draw:fill-color", m_palette.at(16 + curSerNum).name(),
0252               KoGenStyle::GraphicType);
0253     }
0254 }
0255 
0256 QString KoOdfChartWriter::genPlotAreaStyle(KoGenStyles& styles, KoGenStyles& mainStyles)
0257 {
0258     KoGenStyle style(KoGenStyle::ChartAutoStyle/*, "chart"*/);
0259     return genPlotAreaStyle(style, styles, mainStyles);
0260 }
0261 
0262 QString KoOdfChartWriter::replaceSheet(const QString &originalString,
0263                        const QString &replacementSheet)
0264 {
0265     QStringList split = originalString.split(QLatin1Char('!'));
0266     split[0] = replacementSheet;
0267     return split.join(QString::fromLatin1("!"));
0268 }
0269 
0270 void KoOdfChartWriter::set2003ColorPalette(QList < QColor > palette)
0271 {
0272     m_palette = palette;
0273     paletteIsSet = true;
0274 }
0275 
0276 QString KoOdfChartWriter::markerType(KoChart::MarkerType type, int currentSeriesNumber)
0277 {
0278     QString markerName;
0279     switch(type) {
0280         case NoMarker:
0281             break;
0282         case AutoMarker: { // auto marker type
0283             const int resNum = currentSeriesNumber % 3;
0284             if (resNum == 0)
0285                 markerName = "square";
0286             else if (resNum == 1)
0287                 markerName = "diamond";
0288             else if (resNum == 2)
0289                 markerName = "circle";
0290         } break;
0291         case SquareMarker:
0292             markerName = "square";
0293             break;
0294         case DiamondMarker:
0295             markerName = "diamond";
0296             break;
0297         case StarMarker:
0298             markerName = "star";
0299             break;
0300         case TriangleMarker:
0301             markerName = "arrow-up";
0302             break;
0303         case DotMarker:
0304             markerName = "dot";
0305             break;
0306         case PlusMarker:
0307             markerName = "plus";
0308             break;
0309         case SymbolXMarker:
0310             markerName = "x";
0311             break;
0312         case CircleMarker:
0313             markerName = "circle";
0314             break;
0315         case DashMarker:
0316             markerName = "horizontal-bar";
0317             break;
0318     }
0319 
0320     return markerName;
0321 }
0322 
0323 
0324 // ----------------------------------------------------------------
0325 //                 The actual saving code
0326 
0327 
0328 bool KoOdfChartWriter::saveIndex(KoXmlWriter* xmlWriter)
0329 {
0330     if (!chart() || m_href.isEmpty())
0331         return false;
0332 
0333     // This because for presesentations the frame is done in read_graphicFrame
0334     if (!m_drawLayer) {
0335         xmlWriter->startElement("draw:frame");
0336         // used in opendocumentpresentation for layers
0337         //if (m_drawLayer)
0338         //    xmlWriter->addAttribute("draw:layer", "layout");
0339 
0340         // used in opendocumentspreadsheet to reference cells
0341         if (!m_endCellAddress.isEmpty()) {
0342             xmlWriter->addAttribute("table:end-cell-address", m_endCellAddress);
0343             xmlWriter->addAttributePt("table:end-x", m_end_x);
0344             xmlWriter->addAttributePt("table:end-y", m_end_y);
0345         }
0346 
0347         xmlWriter->addAttributePt("svg:x", m_x);
0348         xmlWriter->addAttributePt("svg:y", m_y);
0349         if (m_width > 0)
0350             xmlWriter->addAttributePt("svg:width", m_width);
0351         if (m_height > 0)
0352             xmlWriter->addAttributePt("svg:height", m_height);
0353     }
0354     //xmlWriter->addAttribute("draw:z-index", "0");
0355     xmlWriter->startElement("draw:object");
0356     //TODO don't show on e.g. presenter
0357     if (!m_notifyOnUpdateOfRanges.isEmpty())
0358         xmlWriter->addAttribute("draw:notify-on-update-of-ranges", m_notifyOnUpdateOfRanges);
0359 
0360     xmlWriter->addAttribute("xlink:href", "./" + m_href);
0361     xmlWriter->addAttribute("xlink:type", "simple");
0362     xmlWriter->addAttribute("xlink:show", "embed");
0363     xmlWriter->addAttribute("xlink:actuate", "onLoad");
0364 
0365     xmlWriter->endElement(); // draw:object
0366     if (!m_drawLayer) {
0367         xmlWriter->endElement(); // draw:frame
0368     }
0369     return true;
0370 }
0371 
0372 bool KoOdfChartWriter::saveContent(KoStore* store, KoXmlWriter* manifestWriter)
0373 {
0374     if (!chart() || !chart()->m_impl || m_href.isEmpty())
0375         return false;
0376     
0377     KoGenStyles styles;
0378     KoGenStyles mainStyles;
0379 
0380     store->pushDirectory();
0381     store->enterDirectory(m_href);
0382 
0383     KoOdfWriteStore s(store);
0384     KoXmlWriter* bodyWriter = s.bodyWriter();
0385     KoXmlWriter* contentWriter = s.contentWriter();
0386     Q_ASSERT(bodyWriter && contentWriter);
0387 
0388     bodyWriter->startElement("office:body");
0389     bodyWriter->startElement("office:chart");
0390 
0391     //<chart:chart chart:class="chart:circle"
0392     //             svg:width="8cm" svg:height="7cm"
0393     //             chart:style-name="ch1">
0394     bodyWriter->startElement("chart:chart");
0395 
0396     if (!chart()->m_impl->name().isEmpty()) {
0397         bodyWriter->addAttribute("chart:class", "chart:" + chart()->m_impl->name());
0398     }
0399 
0400     if (m_width > 0) {
0401         bodyWriter->addAttributePt("svg:width", m_width);
0402     }
0403     if (m_height > 0) {
0404         bodyWriter->addAttributePt("svg:height", m_height);
0405     }
0406     
0407     bodyWriter->addAttribute("chart:style-name", genChartAreaStyle(styles, mainStyles));
0408 
0409     // <chart:title svg:x="5.618cm" svg:y="0.14cm" chart:style-name="ch2">
0410     //     <text:p>PIE CHART</text:p>
0411     // </chart:title>
0412     if (!chart()->m_title.isEmpty()) {
0413         bodyWriter->startElement("chart:title");
0414 
0415         /* TODO we can't determine this because by default we need to center the title,
0416         in order to center it we need to know the textbox size, and to do that we need
0417         the used font metrics.
0418 
0419         Also, for now, the default implementation of KChart centers
0420         the title, so we get close to the expected behavior. We ignore any offset though.
0421 
0422         Nonetheless, the formula should be something like this:
0423         const int widht = m_width/2 - textWidth/2 + sprcToPt(t->m_x1, vertical);
0424         const int height = m_height/2 - textHeight/2 + sprcToPt(t->m_y1, horizontal);
0425         bodyWriter->addAttributePt("svg:x", width);
0426         bodyWriter->addAttributePt("svg:y", height);
0427         */
0428 
0429         // NOTE: Don't load width or height, the record MUST be ignored and
0430         //       determined by the application
0431         // see [MS-XLS] p. 362
0432 
0433         bodyWriter->startElement("text:p");
0434         bodyWriter->addTextNode(chart()->m_title);
0435         bodyWriter->endElement(); // text:p
0436         bodyWriter->endElement(); // chart:title
0437     }
0438 
0439     // Legend
0440     if (chart()->m_legend) {
0441         bodyWriter->startElement("chart:legend");
0442         bodyWriter->addAttribute("chart:legend-position", "end");
0443 
0444         KoGenStyle legendstyle(KoGenStyle::ChartAutoStyle, "chart");
0445 
0446         QColor labelColor = labelFontColor();
0447         if (labelColor.isValid())
0448             legendstyle.addProperty("fo:font-color", labelColor.name(), KoGenStyle::TextType);
0449 
0450         bodyWriter->addAttribute("chart:style-name", styles.insert(legendstyle, "lg"));
0451 
0452         bodyWriter->endElement(); // chart:legend
0453     }
0454 
0455     // <chart:plot-area chart:style-name="ch3"
0456     //                  table:cell-range-address="Sheet1.C2:Sheet1.E2"
0457     //                  svg:x="0.16cm" svg:y="0.14cm">
0458     bodyWriter->startElement("chart:plot-area");
0459 
0460     if (chart()->m_is3d) {
0461         //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)");
0462         //bodyWriter->addAttribute("dr3d:vrp", "(12684.722548717 7388.35827488833 17691.2795565958)");
0463         //bodyWriter->addAttribute("dr3d:vpn", "(0.416199821709347 0.173649045905254 0.892537795986984)");
0464         //bodyWriter->addAttribute("dr3d:vup", "(-0.0733876362771618 0.984807599917971 -0.157379306090273)");
0465         //bodyWriter->addAttribute("dr3d:projection", "parallel");
0466         //bodyWriter->addAttribute("dr3d:distance", "4.2cm");
0467         //bodyWriter->addAttribute("dr3d:focal-length", "8cm");
0468         //bodyWriter->addAttribute("dr3d:shadow-slant", "0");
0469         //bodyWriter->addAttribute("dr3d:shade-mode", "flat");
0470         //bodyWriter->addAttribute("dr3d:ambient-color", "#b3b3b3");
0471         //bodyWriter->addAttribute("dr3d:lighting-mode", "true");
0472     }
0473 
0474     KoGenStyle chartstyle(KoGenStyle::ChartAutoStyle, "chart");
0475     //chartstyle.addProperty("chart:connect-bars", "false");
0476     //chartstyle.addProperty("chart:include-hidden-cells", "false");
0477     chartstyle.addProperty("chart:auto-position", "true");
0478     chartstyle.addProperty("chart:auto-size", "true");
0479     chartstyle.addProperty("chart:angle-offset", chart()->m_angleOffset);
0480 
0481     //chartstyle.addProperty("chart:series-source", "rows");
0482     //chartstyle.addProperty("chart:sort-by-x-values", "false");
0483     //chartstyle.addProperty("chart:right-angled-axes", "true");
0484     if (chart()->m_is3d) {
0485         chartstyle.addProperty("chart:three-dimensional", "true");
0486     }
0487     //chartstyle.addProperty("chart:angle-offset", "90");
0488     //chartstyle.addProperty("chart:series-source", "rows");
0489     //chartstyle.addProperty("chart:right-angled-axes", "false");
0490     if (chart()->m_transpose) {
0491         chartstyle.addProperty("chart:vertical", "true");
0492     }
0493     if (chart()->m_stacked) {
0494         chartstyle.addProperty("chart:stacked", "true");
0495     }
0496     if (chart()->m_f100) {
0497         chartstyle.addProperty("chart:percentage", "true");
0498     }
0499     bodyWriter->addAttribute("chart:style-name", genPlotAreaStyle(chartstyle, styles, mainStyles));
0500 
0501     QString verticalCellRangeAddress = chart()->m_verticalCellRangeAddress;
0502 // 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
0503 //     if (!chart()->m_cellRangeAddress.isEmpty()) {
0504 //         if (sheetReplacement)
0505 //             bodyWriter->addAttribute("table:cell-range-address", replaceSheet(normalizeCellRange(m_cellRangeAddress), QString::fromLatin1("local"))); //"Sheet1.C2:Sheet1.E5");
0506 //         else
0507 //             bodyWriter->addAttribute("table:cell-range-address", normalizeCellRange(m_cellRangeAddress)); //"Sheet1.C2:Sheet1.E5");
0508 //     }
0509 
0510     /*FIXME
0511     if (verticalCellRangeAddress.isEmpty()) {
0512         // only add the chart:data-source-has-labels if no chart:categories with a table:cell-range-address was defined within an axis.
0513         bodyWriter->addAttribute("chart:data-source-has-labels", "both");
0514     }
0515     */
0516 
0517     //bodyWriter->addAttribute("svg:x", "0.16cm"); //FIXME
0518     //bodyWriter->addAttribute("svg:y", "0.14cm"); //FIXME
0519     //bodyWriter->addAttribute("svg:width", "6.712cm"); //FIXME
0520     //bodyWriter->addAttribute("svg:height", "6.58cm"); //FIXME
0521 
0522     const bool definesCategories = chart()->m_impl->name() != "scatter"; // scatter charts are using domains
0523     int countXAxis = 0;
0524     int countYAxis = 0;
0525     foreach (KoChart::Axis* axis, chart()->m_axes) {
0526         //TODO handle series-axis
0527         if (axis->m_type == KoChart::Axis::SeriesAxis) continue;
0528 
0529         bodyWriter->startElement("chart:axis");
0530 
0531         KoGenStyle axisstyle(KoGenStyle::ChartAutoStyle, "chart");
0532 
0533         if (axis->m_reversed)
0534             axisstyle.addProperty("chart:reverse-direction", "true", KoGenStyle::ChartType);
0535 
0536         //FIXME this hits an infinite-looping bug in kdchart it seems... maybe fixed with a newer version
0537 //         if (axis->m_logarithmic)
0538 //             axisstyle.addProperty("chart:logarithmic", "true", KoGenStyle::ChartType);
0539 
0540         if (!axis->m_autoMinimum)
0541             axisstyle.addProperty("chart:minimum", QString::number(axis->m_minimum, 'f'),
0542                   KoGenStyle::ChartType);
0543         if (!axis->m_autoMaximum)
0544             axisstyle.addProperty("chart:maximum", QString::number(axis->m_maximum, 'f'),
0545                   KoGenStyle::ChartType);
0546 
0547         axisstyle.addProperty("fo:font-size", QString("%0pt").arg(chart()->m_textSize),
0548                   KoGenStyle::TextType);
0549 
0550         QColor labelColor = labelFontColor();
0551         if (labelColor.isValid())
0552             axisstyle.addProperty("fo:font-color", labelColor.name(), KoGenStyle::TextType);
0553 
0554         if (!axis->m_numberFormat.isEmpty()) {
0555             const KoGenStyle style = NumberFormatParser::parse(axis->m_numberFormat, &styles);
0556             axisstyle.addAttribute("style:data-style-name", styles.insert(style, "ds"));
0557         }
0558 
0559         bodyWriter->addAttribute("chart:style-name", styles.insert(axisstyle, "ch"));
0560 
0561         switch(axis->m_type) {
0562             case KoChart::Axis::VerticalValueAxis:
0563                 bodyWriter->addAttribute("chart:dimension", "y");
0564                 bodyWriter->addAttribute("chart:name", QString("y%1").arg(++countYAxis));
0565                 break;
0566             case KoChart::Axis::HorizontalValueAxis:
0567                 bodyWriter->addAttribute("chart:dimension", "x");
0568                 bodyWriter->addAttribute("chart:name", QString("x%1").arg(++countXAxis));
0569                 if (countXAxis == 1 && definesCategories && !verticalCellRangeAddress.isEmpty()) {
0570                     bodyWriter->startElement("chart:categories");
0571                     if (sheetReplacement)
0572                         verticalCellRangeAddress
0573                 = normalizeCellRange(replaceSheet(verticalCellRangeAddress,
0574                                   QString::fromLatin1("local")));
0575                     else
0576                         verticalCellRangeAddress = normalizeCellRange(verticalCellRangeAddress);
0577                     bodyWriter->addAttribute("table:cell-range-address", verticalCellRangeAddress); //"Sheet1.C2:Sheet1.E2");
0578                     bodyWriter->endElement();
0579                 }
0580                 break;
0581             default: break;
0582         }
0583 
0584         if (axis->m_majorGridlines.m_format.m_style != KoChart::LineFormat::None) {
0585             bodyWriter->startElement("chart:grid");
0586             bodyWriter->addAttribute("chart:class", "major");
0587             bodyWriter->endElement(); // chart:grid
0588         }
0589 
0590         if (axis->m_minorGridlines.m_format.m_style != KoChart::LineFormat::None) {
0591             bodyWriter->startElement("chart:grid");
0592             bodyWriter->addAttribute("chart:class", "minor");
0593             bodyWriter->endElement(); // chart:grid
0594         }
0595         bodyWriter->endElement(); // chart:axis
0596     }
0597 
0598     // Add at least one x-axis.
0599     if (countXAxis == 0) {
0600         bodyWriter->startElement("chart:axis");
0601         bodyWriter->addAttribute("chart:dimension", "x");
0602         bodyWriter->addAttribute("chart:name", "primary-x");
0603 
0604         if (definesCategories && !verticalCellRangeAddress.isEmpty()) {
0605             bodyWriter->startElement("chart:categories");
0606             if (sheetReplacement)
0607                 verticalCellRangeAddress
0608             = normalizeCellRange(replaceSheet(verticalCellRangeAddress,
0609                               QString::fromLatin1("local")));
0610             else
0611                 verticalCellRangeAddress = normalizeCellRange(verticalCellRangeAddress);
0612 
0613             bodyWriter->addAttribute("table:cell-range-address", verticalCellRangeAddress);
0614             bodyWriter->endElement();
0615         }
0616 
0617         bodyWriter->endElement(); // chart:axis
0618     }
0619 
0620     // Add at least one y-axis.
0621     if (countYAxis == 0) {
0622         bodyWriter->startElement("chart:axis");
0623         bodyWriter->addAttribute("chart:dimension", "y");
0624         bodyWriter->addAttribute("chart:name", "primary-y");
0625         bodyWriter->endElement(); // chart:axis
0626     }
0627 
0628     //<chart:axis chart:dimension="x" chart:name="primary-x" chart:style-name="ch4"/>
0629     //<chart:axis chart:dimension="y" chart:name="primary-y" chart:style-name="ch5"><chart:grid chart:style-name="ch6" chart:class="major"/></chart:axis>
0630 
0631     // NOTE: The XLS format specifies that if an explodeFactor that is > 100
0632     //       is found, we should find the biggest and make it 100, then scale
0633     //       all the other factors accordingly.
0634     // see 2.4.195 PieFormat
0635     int maxExplode = 100;
0636     foreach (KoChart::Series* series, chart()->m_series) {
0637         foreach (KoChart::Format* f, series->m_datasetFormat) {
0638             if (KoChart::PieFormat* pieformat = dynamic_cast<KoChart::PieFormat*>(f)) {
0639                 if (pieformat->m_pcExplode > 0) {
0640                     maxExplode = qMax(maxExplode, pieformat->m_pcExplode);
0641                 }
0642             }
0643         }
0644     }
0645 
0646     // Area diagrams are special in that Excel displays the areas in another
0647     // order than OpenOffice.org and Calligra Sheets. To make sure the same areas are
0648     // visible we do the same as OpenOffice.org does and reverse the order.
0649     if (chart()->m_impl->name() == "area") {
0650         for (int i = chart()->m_series.count() - 1; i >= 0; --i) {
0651             chart()->m_series.append(chart()->m_series.takeAt(i));
0652         }
0653     }
0654 
0655     // Save the series.
0656     if (!saveSeries(styles, mainStyles, bodyWriter, maxExplode))
0657     return false;
0658 
0659     bodyWriter->startElement("chart:wall");
0660     bodyWriter->endElement(); // chart:wall
0661 
0662     bodyWriter->startElement("chart:floor");
0663     bodyWriter->endElement(); // chart:floor
0664 
0665     bodyWriter->endElement(); // chart:plot-area
0666 
0667     writeInternalTable(bodyWriter);
0668 
0669     bodyWriter->endElement(); // chart:chart
0670     bodyWriter->endElement(); // office:chart
0671     bodyWriter->endElement(); // office:body
0672 
0673 #ifdef CONTENTXML_DEBUG
0674     debugOdf2 << bodyWriter->toString();
0675 #endif
0676 
0677     styles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
0678     s.closeContentWriter();
0679 
0680     if (store->open("styles.xml")) {
0681         KoStoreDevice dev(store);
0682         KoXmlWriter* stylesWriter = new KoXmlWriter(&dev);
0683 
0684         stylesWriter->startDocument("office:document-styles");
0685         stylesWriter->startElement("office:document-styles");
0686         stylesWriter->addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
0687         stylesWriter->addAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
0688         stylesWriter->addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
0689         stylesWriter->addAttribute("xmlns:table", "urn:oasis:names:tc:opendocument:xmlns:table:1.0");
0690         stylesWriter->addAttribute("xmlns:draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
0691         stylesWriter->addAttribute("xmlns:fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
0692         stylesWriter->addAttribute("xmlns:svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
0693         stylesWriter->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
0694         stylesWriter->addAttribute("xmlns:chart", "urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
0695         stylesWriter->addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
0696         stylesWriter->addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
0697         stylesWriter->addAttribute("xmlns:number", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
0698         stylesWriter->addAttribute("xmlns:dr3d", "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
0699         stylesWriter->addAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
0700         stylesWriter->addAttribute("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
0701         stylesWriter->addAttribute("office:version", "1.2");
0702         mainStyles.saveOdfStyles(KoGenStyles::MasterStyles, stylesWriter);
0703         mainStyles.saveOdfStyles(KoGenStyles::DocumentStyles, stylesWriter); // office:style
0704         mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, stylesWriter); // office:automatic-styles
0705         stylesWriter->endElement();  // office:document-styles
0706         stylesWriter->endDocument();
0707 
0708         delete stylesWriter;
0709         store->close();
0710     }
0711 
0712     manifestWriter->addManifestEntry(m_href + QLatin1Char('/'), "application/vnd.oasis.opendocument.chart");
0713     manifestWriter->addManifestEntry(QString("%1/styles.xml").arg(m_href), "text/xml");
0714     manifestWriter->addManifestEntry(QString("%1/content.xml").arg(m_href), "text/xml");
0715 
0716     store->popDirectory();
0717     return true;
0718 }
0719 
0720 // FIXME: We should probably create a KoOdfChartWriterContext out of these
0721 //        parameters when we add more similar saving functions later.
0722 bool KoOdfChartWriter::saveSeries(KoGenStyles &styles, KoGenStyles &mainStyles,
0723                   KoXmlWriter* bodyWriter, int maxExplode)
0724 {
0725     int curSerNum = 0;
0726     bool lines = true;
0727     bool marker = false;
0728     Q_FOREACH (KoChart::Series* series, chart()->m_series) {
0729         lines = true;
0730         if (chart()->m_impl->name() == "scatter" && !paletteIsSet) {
0731             KoChart::ScatterImpl* impl = static_cast< KoChart::ScatterImpl* >(chart()->m_impl);
0732             lines = (impl->style == KoChart::ScatterImpl::Line
0733              || impl->style == KoChart::ScatterImpl::LineMarker);
0734             marker = (impl->style == KoChart::ScatterImpl::Marker
0735               || impl->style == KoChart::ScatterImpl::LineMarker);
0736         }
0737         const bool noLineFill = ((series->spPr != 0)
0738                  && series->spPr->lineFill.type == KoChart::Fill::None);
0739         lines = lines && !noLineFill;
0740         lines = lines || m_chart->m_showLines;
0741         
0742     // <chart:series chart:style-name="ch7"
0743     //               chart:values-cell-range-address="Sheet1.C2:Sheet1.E2"
0744     //               chart:class="chart:circle">
0745         bodyWriter->startElement("chart:series");
0746         KoGenStyle seriesstyle(KoGenStyle::GraphicAutoStyle, "chart");
0747         if (series->spPr)
0748             addShapePropertyStyle(series, seriesstyle, mainStyles);
0749         else if (lines && paletteIsSet) {
0750             lines = false;
0751             seriesstyle.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);      
0752             seriesstyle.addProperty("svg:stroke-color", m_palette.at(24 + curSerNum).name(),
0753                     KoGenStyle::GraphicType);
0754         }
0755 
0756         if (paletteIsSet
0757         && m_chart->m_impl->name() != "ring"
0758         && m_chart->m_impl->name() != "circle")
0759     {
0760             if (series->m_markerType == KoChart::NoMarker
0761         && m_chart->m_markerType == KoChart::NoMarker
0762         && !marker)
0763             {
0764         seriesstyle.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
0765         seriesstyle.addProperty("draw:fill-color", m_palette.at(16 + curSerNum).name(),
0766                     KoGenStyle::GraphicType);
0767             }
0768         }
0769 
0770         if (series->m_markerType != KoChart::NoMarker) {
0771             QString markerName = markerType(series->m_markerType, curSerNum);
0772             if (!markerName.isEmpty()) {
0773                 seriesstyle.addProperty("chart:symbol-type", "named-symbol", KoGenStyle::ChartType);
0774                 seriesstyle.addProperty("chart:symbol-name", markerName, KoGenStyle::ChartType);
0775             }
0776         }
0777         else if (m_chart->m_markerType != KoChart::NoMarker || marker) {
0778             QString markerName = markerType(m_chart->m_markerType == KoChart::NoMarker
0779                         ? KoChart::AutoMarker
0780                         : m_chart->m_markerType, curSerNum);
0781             if (!markerName.isEmpty()) {
0782                 seriesstyle.addProperty("chart:symbol-type", "named-symbol", KoGenStyle::ChartType);
0783                 seriesstyle.addProperty("chart:symbol-name", markerName, KoGenStyle::ChartType);
0784             }
0785         }
0786 
0787         if (chart()->m_impl->name() != "circle" && chart()->m_impl->name() != "ring")
0788             addDataThemeToStyle(seriesstyle, curSerNum, chart()->m_series.count(), lines);
0789         //seriesstyle.addProperty("draw:stroke", "solid");
0790         //seriesstyle.addProperty("draw:fill-color", "#ff0000");
0791 
0792         foreach (KoChart::Format* f, series->m_datasetFormat) {
0793             if (KoChart::PieFormat* pieformat = dynamic_cast<KoChart::PieFormat*>(f)) {
0794                 if (pieformat->m_pcExplode > 0) {
0795                     // Note that 100.0/maxExplode will yield 1.0 most of the
0796                     // time, that's why do that division first
0797                     const int pcExplode = (int)((float)pieformat->m_pcExplode * (100.0 / (float)maxExplode));
0798                     seriesstyle.addProperty("chart:pie-offset", pcExplode, KoGenStyle::ChartType);
0799                 }
0800             }
0801         }
0802 
0803         if (series->m_showDataLabelValues && series->m_showDataLabelPercent) {
0804             seriesstyle.addProperty("chart:data-label-number", "value-and-percentage",
0805                     KoGenStyle::ChartType);
0806         } else if (series->m_showDataLabelValues) {
0807             seriesstyle.addProperty("chart:data-label-number", "value", KoGenStyle::ChartType);
0808         } else if (series->m_showDataLabelPercent) {
0809             seriesstyle.addProperty("chart:data-label-number", "percentage", KoGenStyle::ChartType);
0810         }
0811 
0812         if (series->m_showDataLabelCategory) {
0813             seriesstyle.addProperty("chart:data-label-text", "true", KoGenStyle::ChartType);
0814         }
0815         //seriesstyle.addProperty("chart:data-label-symbol", "true", KoGenStyle::ChartType);
0816 
0817         if (!series->m_numberFormat.isEmpty()) {
0818             const KoGenStyle style = NumberFormatParser::parse(series->m_numberFormat, &styles);
0819             seriesstyle.addAttribute("style:data-style-name", styles.insert(style, "ds"));
0820         }
0821 
0822         bodyWriter->addAttribute("chart:style-name", styles.insert(seriesstyle, "ch"));
0823 
0824         // ODF does not support custom labels so we depend on the
0825         // SeriesLegendOrTrendlineName being defined and to point to a valid
0826         // cell to be able to display custom labels.
0827         if (series->m_datasetValue.contains(KoChart::Value::SeriesLegendOrTrendlineName)) {
0828             KoChart::Value* v = series->m_datasetValue[KoChart::Value::SeriesLegendOrTrendlineName];
0829             if (!v->m_formula.isEmpty()) {
0830                 bodyWriter->addAttribute("chart:label-cell-address",
0831                      (v->m_type == KoChart::Value::CellRange
0832                       ? normalizeCellRange(v->m_formula)
0833                       : v->m_formula));
0834             }
0835         }
0836 
0837         if (!series->m_labelCell.isEmpty()) {
0838             QString labelAddress = series->m_labelCell;
0839             if (sheetReplacement)
0840                 labelAddress = normalizeCellRange(replaceSheet(labelAddress,
0841                                    QString::fromLatin1("local")));
0842             else
0843                 labelAddress = normalizeCellRange(labelAddress);
0844             bodyWriter->addAttribute("chart:label-cell-address", labelAddress);
0845         }
0846 
0847         QString valuesCellRangeAddress;
0848         if (sheetReplacement)
0849             valuesCellRangeAddress
0850         = normalizeCellRange(replaceSheet(series->m_valuesCellRangeAddress,
0851                           QString::fromLatin1("local")));
0852         else
0853             valuesCellRangeAddress = normalizeCellRange(series->m_valuesCellRangeAddress);
0854         
0855         if (!valuesCellRangeAddress.isEmpty()) {
0856         // "Sheet1.C2:Sheet1.E2";
0857             bodyWriter->addAttribute("chart:values-cell-range-address", valuesCellRangeAddress);
0858         }
0859         else if (!series->m_domainValuesCellRangeAddress.isEmpty()) {
0860         // "Sheet1.C2:Sheet1.E2";
0861             bodyWriter->addAttribute("chart:values-cell-range-address",
0862                      series->m_domainValuesCellRangeAddress.last());
0863         }
0864 
0865         bodyWriter->addAttribute("chart:class", "chart:" + chart()->m_impl->name());
0866 
0867 //         if (chart()->m_impl->name() == "scatter") {
0868 //             bodyWriter->startElement("chart:domain");
0869 //             bodyWriter->addAttribute("table:cell-range-address", verticalCellRangeAddress); //"Sheet1.C2:Sheet1.E5");
0870 //             bodyWriter->endElement();
0871 //         } else if (chart()->m_impl->name() == "bubble") {
0872 
0873             QString domainRange;
0874             Q_FOREACH (const QString& curRange, series->m_domainValuesCellRangeAddress) {
0875                 bodyWriter->startElement("chart:domain");
0876                 if (sheetReplacement)
0877                     domainRange = normalizeCellRange(replaceSheet(curRange, QString::fromLatin1("local")));
0878                 else
0879                     domainRange = normalizeCellRange(curRange);
0880                 if (!domainRange.isEmpty())
0881                     bodyWriter->addAttribute("table:cell-range-address", domainRange);
0882                 bodyWriter->endElement();
0883             }
0884 //             if (series->m_domainValuesCellRangeAddress.count() == 1){
0885 //                 bodyWriter->startElement("chart:domain");
0886 //                 bodyWriter->addAttribute("table:cell-range-address", series->m_domainValuesCellRangeAddress.last()); //"Sheet1.C2:Sheet1.E5");
0887 //                 bodyWriter->endElement();
0888 //             }
0889 //             if (series->m_domainValuesCellRangeAddress.isEmpty()){
0890 //                 bodyWriter->startElement("chart:domain");
0891 //                 bodyWriter->addAttribute("table:cell-range-address", series->m_valuesCellRangeAddress); //"Sheet1.C2:Sheet1.E5");
0892 //                 bodyWriter->endElement();
0893 //                 bodyWriter->startElement("chart:domain");
0894 //                 bodyWriter->addAttribute("table:cell-range-address", series->m_valuesCellRangeAddress); //"Sheet1.C2:Sheet1.E5");
0895 //                 bodyWriter->endElement();
0896 //             }
0897 //         }
0898 
0899         for (int j = 0; j < series->m_countYValues; ++j) {
0900             bodyWriter->startElement("chart:data-point");
0901             KoGenStyle gs(KoGenStyle::GraphicAutoStyle, "chart");
0902 
0903             if (chart()->m_impl->name() == "circle" || chart()->m_impl->name() == "ring") {
0904                 QColor fillColor;
0905                 if (j < series->m_dataPoints.count()) {
0906                     KoChart::DataPoint *dataPoint = series->m_dataPoints[j];
0907                     if (dataPoint->m_areaFormat) {
0908                         fillColor = dataPoint->m_areaFormat->m_foreground;
0909                     }
0910                 }
0911 
0912                 if (fillColor.isValid()) {
0913                     gs.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
0914                     gs.addProperty("draw:fill-color", fillColor.name(), KoGenStyle::GraphicType);
0915                 }
0916                 else if (series->m_markerType == KoChart::NoMarker
0917              && m_chart->m_markerType == KoChart::NoMarker
0918              && !marker)
0919         {
0920                     if (paletteIsSet) {
0921                         gs.addProperty("draw:fill", "solid", KoGenStyle::GraphicType);
0922                         gs.addProperty("draw:fill-color", m_palette.at(16 + j).name(),
0923                        KoGenStyle::GraphicType);
0924                     }
0925                     else {
0926                         addDataThemeToStyle(gs, j, series->m_countYValues, lines);
0927                     }
0928                 }
0929             }/*
0930             else
0931             {
0932                 addSeriesThemeToStyle(gs, curSerNum, chart()->m_series.count());
0933             }*/
0934 
0935             //gs.addProperty("chart:solid-type", "cuboid", KoGenStyle::ChartType);
0936             //gs.addProperty("draw:fill-color",j==0?"#004586":j==1?"#ff420e":"#ffd320",
0937         //               KoGenStyle::GraphicType);
0938             bodyWriter->addAttribute("chart:style-name", styles.insert(gs, "ch"));
0939 
0940             Q_FOREACH (KoChart::Text* t, series->m_texts) {
0941                 bodyWriter->startElement("chart:data-label");
0942                 bodyWriter->startElement("text:p");
0943                 bodyWriter->addTextNode(t->m_text);
0944                 bodyWriter->endElement();
0945                 bodyWriter->endElement();
0946             }
0947 
0948             bodyWriter->endElement();
0949         }
0950         
0951         ++curSerNum;
0952         bodyWriter->endElement(); // chart:series
0953     }
0954 
0955     return true;
0956 }
0957 
0958 
0959 // ----------------------------------------------------------------
0960 //                   Some helper functions
0961 
0962 
0963 // Calculate fade factor as suggested in msoo xml reference page 4161
0964 qreal KoOdfChartWriter::calculateFade(int index, int maxIndex)
0965 {
0966     return -70.0 + 140.0 * ((double) index / ((double) maxIndex + 1.0));
0967 }
0968 
0969 QColor KoOdfChartWriter::shadeColor(const QColor& col, qreal factor)
0970 {
0971     QColor result = col;
0972     qreal luminance = 0.0;
0973     qreal hue = 0.0;
0974     qreal sat = 0.0;
0975     result.getHslF(&hue, &sat, &luminance);
0976     luminance *= factor;
0977     result.setHslF(hue, sat, luminance);
0978     return result;
0979 }
0980 
0981 void KoOdfChartWriter::addDataThemeToStyle(KoGenStyle& style, int dataNumber, int maxNumData,
0982                        bool strokes)
0983 {
0984     // FIXME: This is only relevant to themes, so remove this function after
0985     //        we are done with saveContent().
0986     Q_UNUSED(style);
0987     Q_UNUSED(dataNumber);
0988     Q_UNUSED(maxNumData);
0989     Q_UNUSED(strokes);
0990 }
0991 
0992 
0993 float KoOdfChartWriter::sprcToPt(int sprc, Orientation orientation )
0994 {
0995     if (orientation & vertical)
0996         return (float)sprc * ( (float)m_width / 4000.0);
0997 
0998     return (float)sprc * ( (float)m_height / 4000.0);
0999 }
1000 
1001 void KoOdfChartWriter::writeInternalTable(KoXmlWriter* bodyWriter)
1002 {
1003     Q_ASSERT( bodyWriter );
1004     bodyWriter->startElement("table:table");
1005         bodyWriter->addAttribute( "table:name", "local" );
1006 
1007         bodyWriter->startElement( "table:table-header-columns" );
1008             bodyWriter->startElement( "table:table-column" );
1009             bodyWriter->endElement();
1010         bodyWriter->endElement();
1011 
1012         bodyWriter->startElement( "table:table-columns" );
1013             bodyWriter->startElement( "table:table-column" );
1014             bodyWriter->endElement();
1015         bodyWriter->endElement();
1016 
1017         bodyWriter->startElement( "table:table-rows" );
1018 
1019         const int rowCount = chart()->m_internalTable.maxRow();
1020         for (int r = 1; r <= rowCount; ++r) {
1021             bodyWriter->startElement("table:table-row");
1022             const int columnCount = chart()->m_internalTable.maxCellsInRow(r);
1023             for (int c = 1; c <= columnCount; ++c) {
1024                 bodyWriter->startElement("table:table-cell");
1025                 if (Cell* cell = chart()->m_internalTable.cell(c, r, false)) {
1026                     //debugOdf2 << "cell->m_value " << cell->m_value;
1027                     if (!cell->m_value.isEmpty()) {
1028                         if (!cell->m_valueType.isEmpty()) {
1029                             bodyWriter->addAttribute("office:value-type", cell->m_valueType);
1030                             if (cell->m_valueType == "string") {
1031                                 bodyWriter->addAttribute("office:string-value", cell->m_value);
1032                             } else if (cell->m_valueType == "boolean") {
1033                                 bodyWriter->addAttribute("office:boolean-value", cell->m_value);
1034                             } else if (cell->m_valueType == "date") {
1035                                 bodyWriter->addAttribute("office:date-value", cell->m_value);
1036                             } else if (cell->m_valueType == "time") {
1037                                 bodyWriter->addAttribute("office:time-value", cell->m_value);
1038                             } else { // float, percentage and currency including fraction and scientific
1039                                 bodyWriter->addAttribute("office:value", cell->m_value);
1040                             }
1041                         }
1042 
1043                         bodyWriter->startElement("text:p");
1044                         bodyWriter->addTextNode( cell->m_value );
1045                         bodyWriter->endElement(); // text:p
1046                     }
1047                 }
1048                 bodyWriter->endElement(); // table:table-cell
1049             }
1050             bodyWriter->endElement(); // table:table-row
1051         }
1052         bodyWriter->endElement(); // table:table-rows
1053     bodyWriter->endElement(); // table:table
1054 }
1055 
1056 void KoOdfChartWriter::setSheetReplacement( bool val )
1057 {
1058     sheetReplacement = val;
1059 }