File indexing completed on 2025-01-12 13:05:33

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