diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5a90a21b..778090a8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4547,7 +4547,7 @@ parameters: - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 43 + count: 42 path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - diff --git a/samples/Chart/33_Chart_create_bubble.php b/samples/Chart/33_Chart_create_bubble.php new file mode 100644 index 00000000..33feea62 --- /dev/null +++ b/samples/Chart/33_Chart_create_bubble.php @@ -0,0 +1,124 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['Number of Products', 'Sales in USD', 'Market share'], + [14, 12200, 15], + [20, 60000, 33], + [18, 24400, 10], + [22, 32000, 42], + [], + [12, 8200, 18], + [15, 50000, 30], + [19, 22400, 15], + [25, 25000, 50], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker + +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['2013']), // 2013 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['2014']), // 2014 +]; + +// Set the X-Axis values +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesCategories = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$7:$A$10', null, 4), +]; + +// Set the Y-Axis values +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$7:$B$10', null, 4), +]; + +// Set the Z-Axis values (bubble size) +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesBubbles = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$7:$C$10', null, 4), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_BUBBLECHART, // plotType + null, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $dataSeriesCategories, // plotCategory + $dataSeriesValues // plotValues +); +$series->setPlotBubbleSizes($dataSeriesBubbles); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + +// Create the chart +$chart = new Chart( + 'chart1', // name + null, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('E1'); +$chart->setBottomRightPosition('M15'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); +$worksheet->getColumnDimension('A')->setAutoSize(true); +$worksheet->getColumnDimension('B')->setAutoSize(true); +$worksheet->getColumnDimension('C')->setAutoSize(true); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/samples/templates/32readwriteBubbleChart2.xlsx b/samples/templates/32readwriteBubbleChart2.xlsx new file mode 100644 index 00000000..206cfaea Binary files /dev/null and b/samples/templates/32readwriteBubbleChart2.xlsx differ diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php index 80d3d5f3..ec6342c5 100644 --- a/src/PhpSpreadsheet/Chart/Chart.php +++ b/src/PhpSpreadsheet/Chart/Chart.php @@ -152,6 +152,9 @@ class Chart /** @var ?int */ private $perspective; + /** @var bool */ + private $oneCellAnchor = false; + /** * Create a new Chart. * @@ -743,4 +746,16 @@ class Chart return $this; } + + public function getOneCellAnchor(): bool + { + return $this->oneCellAnchor; + } + + public function setOneCellAnchor(bool $oneCellAnchor): self + { + $this->oneCellAnchor = $oneCellAnchor; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php index 067d30e5..dca1186e 100644 --- a/src/PhpSpreadsheet/Chart/DataSeries.php +++ b/src/PhpSpreadsheet/Chart/DataSeries.php @@ -107,6 +107,13 @@ class DataSeries */ private $plotValues = []; + /** + * Plot Bubble Sizes. + * + * @var DataSeriesValues[] + */ + private $plotBubbleSizes = []; + /** * Create a new DataSeries. * @@ -339,6 +346,28 @@ class DataSeries return false; } + /** + * Get Plot Bubble Sizes. + * + * @return DataSeriesValues[] + */ + public function getPlotBubbleSizes(): array + { + return $this->plotBubbleSizes; + } + + /** + * Set Plot Bubble Sizes. + * + * @param DataSeriesValues[] $plotBubbleSizes + */ + public function setPlotBubbleSizes(array $plotBubbleSizes): self + { + $this->plotBubbleSizes = $plotBubbleSizes; + + return $this; + } + /** * Get Number of Plot Series. * diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index cf3e0853..6747934a 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -328,7 +328,7 @@ class DataSeriesValues */ public function isMultiLevelSeries() { - if (count($this->dataValues) > 0) { + if (!empty($this->dataValues)) { return is_array(array_values($this->dataValues)[0]); } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 455fe07a..8562339b 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -157,6 +157,10 @@ class Xlsx extends BaseReader Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, ]; + private const REL_TO_CHART = [ + Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART, + ]; + /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * @@ -408,17 +412,21 @@ class Xlsx extends BaseReader // Read the theme first, because we need the colour scheme when reading the styles [$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName(); + $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML; + $chartNS = self::REL_TO_CHART[$xmlNamespaceBase] ?? Namespaces::CHART; $wbRels = $this->loadZip("xl/_rels/${workbookBasename}.rels", Namespaces::RELATIONSHIPS); $theme = null; $this->styleReader = new Styles(); foreach ($wbRels->Relationship as $relx) { $rel = self::getAttributes($relx); $relTarget = (string) $rel['Target']; + if (substr($relTarget, 0, 4) === '/xl/') { + $relTarget = substr($relTarget, 4); + } switch ($rel['Type']) { case "$xmlNamespaceBase/theme": $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; $themeOrderAdditional = count($themeOrderArray); - $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML; $xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS); $xmlThemeName = self::getAttributes($xmlTheme); @@ -1204,12 +1212,20 @@ class Xlsx extends BaseReader . '/_rels/' . basename($fileWorksheet) . '.rels'; + if (substr($drawingFilename, 0, 7) === 'xl//xl/') { + $drawingFilename = substr($drawingFilename, 4); + } if ($zip->locateName($drawingFilename)) { $relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS); $drawings = []; foreach ($relsWorksheet->Relationship as $ele) { if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { - $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); + $eleTarget = (string) $ele['Target']; + if (substr($eleTarget, 0, 4) === '/xl/') { + $drawings[(string) $ele['Id']] = substr($eleTarget, 1); + } else { + $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); + } } } @@ -1234,7 +1250,13 @@ class Xlsx extends BaseReader $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']); } elseif ($eleType === "$xmlNamespaceBase/chart") { if ($this->includeCharts) { - $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [ + $eleTarget = (string) $ele['Target']; + if (substr($eleTarget, 0, 4) === '/xl/') { + $index = substr($eleTarget, 1); + } else { + $index = self::dirAdd($fileDrawing, $eleTarget); + } + $charts[$index] = [ 'id' => (string) $ele['Id'], 'sheet' => $docSheet->getTitle(), ]; @@ -1326,6 +1348,7 @@ class Xlsx extends BaseReader 'width' => $width, 'height' => $height, 'worksheetTitle' => $docSheet->getTitle(), + 'oneCellAnchor' => true, ]; } } @@ -1645,7 +1668,7 @@ class Xlsx extends BaseReader if ($this->includeCharts) { $chartEntryRef = ltrim((string) $contentType['PartName'], '/'); $chartElements = $this->loadZip($chartEntryRef); - $chartReader = new Chart(); + $chartReader = new Chart($chartNS, $drawingNS); $objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml')); if (isset($charts[$chartEntryRef])) { $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id']; @@ -1662,6 +1685,9 @@ class Xlsx extends BaseReader // oneCellAnchor or absoluteAnchor (e.g. Chart sheet) $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); $objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']); + if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) { + $objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']); + } } } } @@ -1823,6 +1849,11 @@ class Xlsx extends BaseReader private static function dirAdd($base, $add): string { + $add = "$add"; + if (substr($add, 0, 4) === '/xl/') { + $add = substr($add, 4); + } + return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add"); } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index f6b1edd5..98507af8 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -305,7 +305,7 @@ class Chart { $multiSeriesType = null; $smoothLine = false; - $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = []; + $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = []; $seriesDetailSet = $chartDetail->children($this->cNamespace); foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) { @@ -382,6 +382,10 @@ class Chart case 'yVal': $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize"); + break; + case 'bubbleSize': + $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize"); + break; case 'bubble3D': $bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean'); @@ -435,9 +439,11 @@ class Chart } } } - /** @phpstan-ignore-next-line */ - return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine); + $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine); + $series->setPlotBubbleSizes($seriesBubbles); + + return $series; } /** @@ -494,6 +500,16 @@ class Chart return $seriesValues; } + if (isset($seriesDetail->v)) { + return new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_STRING, + null, + null, + 1, + [(string) $seriesDetail->v] + ); + } + return null; } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php index eb768d09..57a88bb0 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php @@ -76,5 +76,7 @@ class Namespaces const PURL_DRAWING = 'http://purl.oclc.org/ooxml/drawingml/main'; + const PURL_CHART = 'http://purl.oclc.org/ooxml/drawingml/chart'; + const PURL_WORKSHEET = 'http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet'; } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index 4ea26438..08d578e6 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -284,9 +284,12 @@ class Chart extends WriterPart $objWriter->endElement(); } } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) { - $objWriter->startElement('c:bubbleScale'); - $objWriter->writeAttribute('val', 25); - $objWriter->endElement(); + $scale = ($plotGroup === null) ? '' : (string) $plotGroup->getPlotStyle(); + if ($scale !== '') { + $objWriter->startElement('c:bubbleScale'); + $objWriter->writeAttribute('val', $scale); + $objWriter->endElement(); + } $objWriter->startElement('c:showNegBubbles'); $objWriter->writeAttribute('val', 0); @@ -1326,7 +1329,23 @@ class Chart extends WriterPart } if ($groupType === DataSeries::TYPE_BUBBLECHART) { - $this->writeBubbles($plotSeriesValues, $objWriter); + if (!empty($plotGroup->getPlotBubbleSizes()[$plotSeriesIdx])) { + $objWriter->startElement('c:bubbleSize'); + $this->writePlotSeriesValues( + $plotGroup->getPlotBubbleSizes()[$plotSeriesIdx], + $objWriter, + $groupType, + 'num' + ); + $objWriter->endElement(); + if ($plotSeriesValues !== false) { + $objWriter->startElement('c:bubble3D'); + $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0'); + $objWriter->endElement(); + } + } else { + $this->writeBubbles($plotSeriesValues, $objWriter); + } } $objWriter->endElement(); @@ -1420,38 +1439,43 @@ class Chart extends WriterPart $objWriter->writeRawData($plotSeriesValues->getDataSource()); $objWriter->endElement(); - $objWriter->startElement('c:' . $dataType . 'Cache'); + $count = $plotSeriesValues->getPointCount(); + $source = $plotSeriesValues->getDataSource(); + $values = $plotSeriesValues->getDataValues(); + if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) { + $objWriter->startElement('c:' . $dataType . 'Cache'); - if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) { - if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) { - $objWriter->startElement('c:formatCode'); - $objWriter->writeRawData($plotSeriesValues->getFormatCode()); - $objWriter->endElement(); - } - } - - $objWriter->startElement('c:ptCount'); - $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); - $objWriter->endElement(); - - $dataValues = $plotSeriesValues->getDataValues(); - if (!empty($dataValues)) { - if (is_array($dataValues)) { - foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { - $objWriter->startElement('c:pt'); - $objWriter->writeAttribute('idx', $plotSeriesKey); - - $objWriter->startElement('c:v'); - $objWriter->writeRawData($plotSeriesValue); - $objWriter->endElement(); + if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) { + if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) { + $objWriter->startElement('c:formatCode'); + $objWriter->writeRawData($plotSeriesValues->getFormatCode()); $objWriter->endElement(); } } + + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); + $objWriter->endElement(); + + $dataValues = $plotSeriesValues->getDataValues(); + if (!empty($dataValues)) { + if (is_array($dataValues)) { + foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $plotSeriesKey); + + $objWriter->startElement('c:v'); + $objWriter->writeRawData($plotSeriesValue); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + } + + $objWriter->endElement(); // *Cache } - $objWriter->endElement(); - - $objWriter->endElement(); + $objWriter->endElement(); // *Ref } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php index 33eee1e0..7693c72c 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -108,6 +108,19 @@ class Drawing extends WriterPart $objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1)); $objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset'])); $objWriter->endElement(); + } elseif ($chart->getOneCellAnchor()) { + $objWriter->startElement('xdr:oneCellAnchor'); + + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1)); + $objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset'])); + $objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1)); + $objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset'])); + $objWriter->endElement(); + $objWriter->startElement('xdr:ext'); + $objWriter->writeAttribute('cx', self::stringEmu($br['xOffset'])); + $objWriter->writeAttribute('cy', self::stringEmu($br['yOffset'])); + $objWriter->endElement(); } else { $objWriter->startElement('xdr:absoluteAnchor'); $objWriter->startElement('xdr:pos'); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php new file mode 100644 index 00000000..a7343af5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php @@ -0,0 +1,115 @@ +setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame(1, $sheet->getChartCount()); + + self::assertSame('Sheet', $sheet->getTitle()); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + self::assertEmpty($chart->getTitle()); + self::assertTrue($chart->getOneCellAnchor()); + + $plotArea = $chart->getPlotArea(); + $plotSeries = $plotArea->getPlotGroup(); + self::assertCount(1, $plotSeries); + $dataSeries = $plotSeries[0]; + $labels = $dataSeries->getPlotLabels(); + self::assertCount(2, $labels); + self::assertSame(['2013'], $labels[0]->getDataValues()); + self::assertSame(['2014'], $labels[1]->getDataValues()); + + $plotCategories = $dataSeries->getPlotCategories(); + self::assertCount(2, $plotCategories); + $categories = $plotCategories[0]; + self::assertSame('Number', $categories->getDataType()); + self::assertSame('\'Sheet\'!$A$2:$A$5', $categories->getDataSource()); + self::assertFalse($categories->getBubble3D()); + $categories = $plotCategories[1]; + self::assertCount(2, $plotCategories); + self::assertSame('Number', $categories->getDataType()); + self::assertSame('\'Sheet\'!$A$7:$A$10', $categories->getDataSource()); + self::assertFalse($categories->getBubble3D()); + + $plotValues = $dataSeries->getPlotValues(); + self::assertCount(2, $plotValues); + $values = $plotValues[0]; + self::assertSame('Number', $values->getDataType()); + self::assertSame('\'Sheet\'!$B$2:$B$5', $values->getDataSource()); + self::assertFalse($values->getBubble3D()); + $values = $plotValues[1]; + self::assertCount(2, $plotValues); + self::assertSame('Number', $values->getDataType()); + self::assertSame('\'Sheet\'!$B$7:$B$10', $values->getDataSource()); + self::assertFalse($values->getBubble3D()); + + $plotValues = $dataSeries->getPlotBubbleSizes(); + self::assertCount(2, $plotValues); + $values = $plotValues[0]; + self::assertSame('Number', $values->getDataType()); + self::assertSame('\'Sheet\'!$C$2:$C$5', $values->getDataSource()); + self::assertFalse($values->getBubble3D()); + $values = $plotValues[1]; + self::assertCount(2, $plotValues); + self::assertSame('Number', $values->getDataType()); + self::assertSame('\'Sheet\'!$C$7:$C$10', $values->getDataSource()); + self::assertFalse($values->getBubble3D()); + + $spreadsheet->disconnectWorksheets(); + } + + public function testXml(): void + { + $infile = self::DIRECTORY . '32readwriteBubbleChart2.xlsx'; + $file = 'zip://'; + $file .= $infile; + $file .= '#xl/charts/chart1.xml'; + $data = file_get_contents($file); + // confirm that file contains expected tags + if ($data === false) { + self::fail('Unable to read file'); + } else { + self::assertSame(0, substr_count($data, 'c:'), 'unusual choice of prefix'); + self::assertSame(0, substr_count($data, 'bubbleScale')); + self::assertSame(1, substr_count($data, '2013'), 'v tag for 2013'); + self::assertSame(1, substr_count($data, '2014'), 'v tag for 2014'); + self::assertSame(0, substr_count($data, 'numCache'), 'no cached values'); + } + $file = 'zip://'; + $file .= $infile; + $file .= '#xl/drawings/_rels/drawing1.xml.rels'; + $data = file_get_contents($file); + // confirm that file contains expected tags + if ($data === false) { + self::fail('Unable to read file'); + } else { + self::assertSame(1, substr_count($data, 'Target="/xl/charts/chart1.xml"'), 'Unusual absolute address in drawing rels file'); + } + $file = 'zip://'; + $file .= $infile; + $file .= '#xl/worksheets/_rels/sheet1.xml.rels'; + $data = file_get_contents($file); + // confirm that file contains expected tags + if ($data === false) { + self::fail('Unable to read file'); + } else { + self::assertSame(1, substr_count($data, 'Target="/xl/drawings/drawing1.xml"'), 'Unusual absolute address in worksheet rels file'); + } + } +}