See Release Notes
Long Term Support Release
<?php namespace PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Cell\Coordinate;> use PhpOffice\PhpSpreadsheet\Cell\DataType;use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;< use PhpOffice\PhpSpreadsheet\NamedRange;> use PhpOffice\PhpSpreadsheet\DefinedName;use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;> use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;> use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Theme; use PhpOffice\PhpSpreadsheet\ReferenceHelper; > use PhpOffice\PhpSpreadsheet\Reader\Xlsx\WorkbookView;use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\Drawing; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\Font; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet;< use PhpOffice\PhpSpreadsheet\Style\Border; < use PhpOffice\PhpSpreadsheet\Style\Borders;use PhpOffice\PhpSpreadsheet\Style\Color;> use PhpOffice\PhpSpreadsheet\Style\Font as StyleFont;use PhpOffice\PhpSpreadsheet\Style\NumberFormat;< use PhpOffice\PhpSpreadsheet\Style\Protection;use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement;> use Throwable;use XMLReader; use ZipArchive; class Xlsx extends BaseReader {> const INITIAL_FILE = '_rels/.rels'; /** >* ReferenceHelper instance. * * @var ReferenceHelper */ private $referenceHelper; /**< * Xlsx\Theme instance. < * < * @var Xlsx\Theme> * @var ZipArchive*/< private static $theme = null;> private $zip; > > /** @var Styles */ > private $styleReader;/** * Create a new Xlsx Reader instance. */ public function __construct() { parent::__construct(); $this->referenceHelper = ReferenceHelper::getInstance(); $this->securityScanner = XmlScanner::getInstance($this); } /** * Can the current IReader read the file?< * < * @param string $pFilename < * < * @throws Exception < * < * @return bool*/< public function canRead($pFilename)> public function canRead(string $filename): bool{< File::assertFile($pFilename);> if (!File::testFileNoThrow($filename, self::INITIAL_FILE)) { > return false; > }$result = false;< $zip = new ZipArchive();> $this->zip = $zip = new ZipArchive();< if ($zip->open($pFilename) === true) { < $workbookBasename = $this->getWorkbookBaseName($zip);> if ($zip->open($filename) === true) { > [$workbookBasename] = $this->getWorkbookBaseName();$result = !empty($workbookBasename); $zip->close(); } return $result; } /**> * @param mixed $value * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. > */ * > public static function testSimpleXml($value): SimpleXMLElement * @param string $pFilename > { * > return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>'); * @throws Exception > } * > * @return array > public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement */ > { public function listWorksheetNames($pFilename) > return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); { > } File::assertFile($pFilename); > > // Phpstan thinks, correctly, that xpath can return false. $worksheetNames = []; > // Scrutinizer thinks it can't. > // Sigh. $zip = new ZipArchive(); > private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array $zip->open($pFilename); > { > return self::falseToArray($sxml->xpath($path)); // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader > } //~ http://schemas.openxmlformats.org/package/2006/relationships"); > $rels = simplexml_load_string( > /** $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')) > * @param mixed $value ); > */ foreach ($rels->Relationship as $rel) { > public static function falseToArray($value): array switch ($rel['Type']) { > { case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': > return is_array($value) ? $value : []; //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" > } $xmlWorkbook = simplexml_load_string( > $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")) > private function loadZip(string $filename, string $ns = ''): SimpleXMLElement ); > { > $contents = $this->getFromZipArchive($this->zip, $filename); if ($xmlWorkbook->sheets) { > $rels = simplexml_load_string( foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { > $this->securityScanner->scan($contents), // Check if sheet should be skipped > 'SimpleXMLElement', $worksheetNames[] = (string) $eleSheet['name']; > Settings::getLibXmlLoaderOptions(), } > $ns } > ); } > } > return self::testSimpleXml($rels); > } $zip->close(); > > // This function is just to identify cases where I'm not sure return $worksheetNames; > // why empty namespace is required. } > private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement > { /** > $contents = $this->getFromZipArchive($this->zip, $filename); * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). > $rels = simplexml_load_string( * > $this->securityScanner->scan($contents), * @param string $pFilename > 'SimpleXMLElement', * > Settings::getLibXmlLoaderOptions(), * @throws Exception > ($ns === '' ? $ns : '') * > ); * @return array > */ > return self::testSimpleXml($rels); public function listWorksheetInfo($pFilename) > } { > File::assertFile($pFilename); > private const REL_TO_MAIN = [ > Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN, $worksheetInfo = []; > Namespaces::THUMBNAIL => '', > ]; $zip = new ZipArchive(); > $zip->open($pFilename); > private const REL_TO_DRAWING = [ > Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, //~ http://schemas.openxmlformats.org/package/2006/relationships" > ]; $rels = simplexml_load_string( > $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), > private const REL_TO_CHART = [ 'SimpleXMLElement', > Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART, Settings::getLibXmlLoaderOptions() > ]; ); > foreach ($rels->Relationship as $rel) { > /**< * @param string $pFilename < * < * @throws Exception> * @param string $filename< public function listWorksheetNames($pFilename)> public function listWorksheetNames($filename)< File::assertFile($pFilename);> File::assertFile($filename, self::INITIAL_FILE);< $zip = new ZipArchive(); < $zip->open($pFilename);> $this->zip = $zip = new ZipArchive(); > $zip->open($filename);< //~ http://schemas.openxmlformats.org/package/2006/relationships"); < $rels = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')) < ); < foreach ($rels->Relationship as $rel) { < switch ($rel['Type']) { < case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': < //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" < $xmlWorkbook = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")) < );> $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); > foreach ($rels->Relationship as $relx) { > $rel = self::getAttributes($relx); > $relType = (string) $rel['Type']; > $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; > if ($mainNS !== '') { > $xmlWorkbook = $this->loadZip((string) $rel['Target'], $mainNS);< $worksheetNames[] = (string) $eleSheet['name'];> $worksheetNames[] = (string) self::getAttributes($eleSheet)['name'];< * @param string $pFilename < * < * @throws Exception> * @param string $filename< public function listWorksheetInfo($pFilename)> public function listWorksheetInfo($filename)< File::assertFile($pFilename);> File::assertFile($filename, self::INITIAL_FILE);< $zip = new ZipArchive(); < $zip->open($pFilename);> $this->zip = $zip = new ZipArchive(); > $zip->open($filename);< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $rels = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < foreach ($rels->Relationship as $rel) { < if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') { < $dir = dirname($rel['Target']); < < //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorkbook = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');> $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); > foreach ($rels->Relationship as $relx) { > $rel = self::getAttributes($relx); > $relType = (string) $rel['Type']; > $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; > if ($mainNS !== '') { > $relTarget = (string) $rel['Target']; > $dir = dirname($relTarget); > $namespace = dirname($relType); > $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', '');< foreach ($relsWorkbook->Relationship as $ele) { < if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet') {> foreach ($relsWorkbook->Relationship as $elex) { > $ele = self::getAttributes($elex); > if ( > ((string) $ele['Type'] === "$namespace/worksheet") || > ((string) $ele['Type'] === "$namespace/chartsheet") > ) {< //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" < $xmlWorkbook = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, "{$rel['Target']}") < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $xmlWorkbook = $this->loadZip($relTarget, $mainNS);< $dir = dirname($rel['Target']);> $dir = dirname($relTarget);< 'worksheetName' => (string) $eleSheet['name'],> 'worksheetName' => (string) self::getAttributes($eleSheet)['name'],< $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];> $fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')]; > $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet";< 'zip://' . File::realpath($pFilename) . '#' . "$dir/$fileWorksheet"> 'zip://' . File::realpath($filename) . '#' . $fileWorksheetPath< if ($xml->name == 'row' && $xml->nodeType == XMLReader::ELEMENT) {> if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) {< } elseif ($xml->name == 'c' && $xml->nodeType == XMLReader::ELEMENT) { < ++$currCells;> } elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { > $cell = $xml->getAttribute('r'); > $currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1);} $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $xml->close(); $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1; $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); $worksheetInfo[] = $tmpInfo; } } } } $zip->close(); return $worksheetInfo; }< private static function castToBoolean($c)> private static function castToBoolean(SimpleXMLElement $c): bool{ $value = isset($c->v) ? (string) $c->v : null; if ($value == '0') { return false; } elseif ($value == '1') { return true; } return (bool) $c->v; }< private static function castToError($c)> private static function castToError(?SimpleXMLElement $c): ?string{< return isset($c->v) ? (string) $c->v : null;> return isset($c, $c->v) ? (string) $c->v : null;}< private static function castToString($c)> private static function castToString(?SimpleXMLElement $c): ?string{< return isset($c->v) ? (string) $c->v : null;> return isset($c, $c->v) ? (string) $c->v : null;}< private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType)> /** > * @param mixed $value > * @param mixed $calculatedValue > */ > private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, array &$sharedFormulas, string $castBaseType): void{> if ($c === null) { $cellDataType = 'f'; > return; $value = "={$c->f}"; > } $calculatedValue = self::$castBaseType($c); > $attr = $c->f->attributes();// Shared formula?< if (isset($c->f['t']) && strtolower((string) $c->f['t']) == 'shared') { < $instance = (string) $c->f['si'];> if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') { > $instance = (string) $attr['si'];< if (!isset($sharedFormulas[(string) $c->f['si']])) {> if (!isset($sharedFormulas[(string) $attr['si']])) {$sharedFormulas[$instance] = ['master' => $r, 'formula' => $value]; } else {< $master = Coordinate::coordinateFromString($sharedFormulas[$instance]['master']); < $current = Coordinate::coordinateFromString($r);> $master = Coordinate::indexesFromString($sharedFormulas[$instance]['master']); > $current = Coordinate::indexesFromString($r);$difference = [0, 0];< $difference[0] = Coordinate::columnIndexFromString($current[0]) - Coordinate::columnIndexFromString($master[0]);> $difference[0] = $current[0] - $master[0];$difference[1] = $current[1] - $master[1]; $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]); } } } /**< * @param ZipArchive $archive> * @param string $fileName > */ > private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool > { > // Root-relative paths > if (strpos($fileName, '//') !== false) { > $fileName = substr($fileName, strpos($fileName, '//') + 1); > } > $fileName = File::realpath($fileName); > > // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming > // so we need to load case-insensitively from the zip file > > // Apache POI fixes > $contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE); > if ($contents === false) { > $contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE); > } > > return $contents !== false; > } > > /*** @param string $fileName * * @return string */ private function getFromZipArchive(ZipArchive $archive, $fileName = '') { // Root-relative paths if (strpos($fileName, '//') !== false) { $fileName = substr($fileName, strpos($fileName, '//') + 1); }> // Relative paths generated by dirname($filename) when $filename $fileName = File::realpath($fileName); > // has no path (i.e.files in root of the zip archive) > $fileName = (string) preg_replace('/^\.\//', '', $fileName);// Sadly, some 3rd party xlsx generators don't use consistent case for filenaming // so we need to load case-insensitively from the zip file // Apache POI fixes $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE); if ($contents === false) { $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE); }< return $contents;> return ($contents === false) ? '' : $contents;} /** * Loads Spreadsheet from file.< * < * @param string $pFilename < * < * @throws Exception < * < * @return Spreadsheet*/< public function load($pFilename)> protected function loadSpreadsheetFromFile(string $filename): Spreadsheet{< File::assertFile($pFilename);> File::assertFile($filename, self::INITIAL_FILE);// Initialisations $excel = new Spreadsheet(); $excel->removeSheetByIndex(0);< if (!$this->readDataOnly) { < $excel->removeCellStyleXfByIndex(0); // remove the default style < $excel->removeCellXfByIndex(0); // remove the default style < }> $addingFirstCellStyleXf = true; > $addingFirstCellXf = true; >$unparsedLoadedData = [];< $zip = new ZipArchive(); < $zip->open($pFilename);> $this->zip = $zip = new ZipArchive(); > $zip->open($filename);// Read the theme first, because we need the colour scheme when reading the styles< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $workbookBasename = $this->getWorkbookBaseName($zip); < $wbRels = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < foreach ($wbRels->Relationship as $rel) {> [$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 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':> case "$xmlNamespaceBase/theme":$themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; $themeOrderAdditional = count($themeOrderArray);< $xmlTheme = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < if (is_object($xmlTheme)) { < $xmlThemeName = $xmlTheme->attributes(); < $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');> $xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS); > $xmlThemeName = self::getAttributes($xmlTheme); > $xmlTheme = $xmlTheme->children($drawingNS);$themeName = (string) $xmlThemeName['name'];< $colourScheme = $xmlTheme->themeElements->clrScheme->attributes();> $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme);$colourSchemeName = (string) $colourScheme['name'];< $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');> $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS);$themeColours = []; foreach ($colourScheme as $k => $xmlColour) { $themePos = array_search($k, $themeOrderArray); if ($themePos === false) { $themePos = $themeOrderAdditional++; } if (isset($xmlColour->sysClr)) {< $xmlColourData = $xmlColour->sysClr->attributes(); < $themeColours[$themePos] = $xmlColourData['lastClr'];> $xmlColourData = self::getAttributes($xmlColour->sysClr); > $themeColours[$themePos] = (string) $xmlColourData['lastClr'];} elseif (isset($xmlColour->srgbClr)) {< $xmlColourData = $xmlColour->srgbClr->attributes(); < $themeColours[$themePos] = $xmlColourData['val'];> $xmlColourData = self::getAttributes($xmlColour->srgbClr); > $themeColours[$themePos] = (string) $xmlColourData['val'];} }< self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours); < }> $theme = new Theme($themeName, $colourSchemeName, $themeColours); > $this->styleReader->setTheme($theme);break; } }< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $rels = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS);$propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties());< foreach ($rels->Relationship as $rel) { < switch ($rel['Type']) { < case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties': < $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));> foreach ($rels->Relationship as $relx) { > $rel = self::getAttributes($relx); > $relTarget = (string) $rel['Target']; > $relType = (string) $rel['Type']; > $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; > switch ($relType) { > case Namespaces::CORE_PROPERTIES: > $propertyReader->readCoreProperties($this->getFromZipArchive($zip, $relTarget));break;< case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties': < $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));> case "$xmlNamespaceBase/extended-properties": > $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $relTarget));break;< case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties': < $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));> case "$xmlNamespaceBase/custom-properties": > $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $relTarget));break; //Ribbon< case 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility': < $customUI = $rel['Target']; < if ($customUI !== null) {> case Namespaces::EXTENSIBILITY: > $customUI = $relTarget; > if ($customUI) {$this->readRibbon($excel, $customUI, $zip); } break;< case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': < $dir = dirname($rel['Target']); < //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorkbook = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');> case "$xmlNamespaceBase/officeDocument": > $dir = dirname($relTarget); > > // Do not specify namespace in next stmt - do it in Xpath > $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); > $relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS);$sharedStrings = [];< $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']"));> $relType = "rel:Relationship[@Type='" > //. Namespaces::SHARED_STRINGS > . "$xmlNamespaceBase/sharedStrings" > . "']"; > $xpath = self::getArrayItem($relsWorkbook->xpath($relType)); >if ($xpath) {< //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" < $xmlStrings = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < if (isset($xmlStrings, $xmlStrings->si)) {> $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS); > if (isset($xmlStrings->si)) {foreach ($xmlStrings->si as $val) { if (isset($val->t)) { $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t); } elseif (isset($val->r)) { $sharedStrings[] = $this->parseRichText($val); } } } } $worksheets = []; $macros = $customUI = null;< foreach ($relsWorkbook->Relationship as $ele) {> foreach ($relsWorkbook->Relationship as $elex) { > $ele = self::getAttributes($elex);switch ($ele['Type']) {< case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':> case Namespaces::WORKSHEET: > case Namespaces::PURL_WORKSHEET:$worksheets[(string) $ele['Id']] = $ele['Target']; break;> case Namespaces::CHARTSHEET: // a vbaProject ? (: some macros) > if ($this->includeCharts === true) { case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject': > $worksheets[(string) $ele['Id']] = $ele['Target']; $macros = $ele['Target']; > } > break; > break;< case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject':> case Namespaces::VBA:} if ($macros !== null) { $macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin if ($macrosCode !== false) { $excel->setMacrosCode($macrosCode); $excel->setHasMacros(true); //short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir $Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin'); if ($Certificate !== false) { $excel->setMacrosCertificate($Certificate); } } }< $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']")); < //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" < $xmlStyles = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $relType = "rel:Relationship[@Type='" > . "$xmlNamespaceBase/styles" > . "']"; > $xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType)); > > if ($xpath === null) { > $xmlStyles = self::testSimpleXml(null); > } else { > $xmlStyles = $this->loadZip("$dir/$xpath[Target]", $mainNS); > } > > $palette = self::extractPalette($xmlStyles); > $this->styleReader->setWorkbookPalette($palette); > $fills = self::extractStyles($xmlStyles, 'fills', 'fill'); > $fonts = self::extractStyles($xmlStyles, 'fonts', 'font'); > $borders = self::extractStyles($xmlStyles, 'borders', 'border'); > $xfTags = self::extractStyles($xmlStyles, 'cellXfs', 'xf'); > $cellXfTags = self::extractStyles($xmlStyles, 'cellStyleXfs', 'xf');$styles = []; $cellStyles = []; $numFmts = null;< if ($xmlStyles && $xmlStyles->numFmts[0]) {> if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) {$numFmts = $xmlStyles->numFmts[0]; } if (isset($numFmts) && ($numFmts !== null)) {< $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');> $numFmts->registerXPathNamespace('sml', $mainNS);}< if (!$this->readDataOnly && $xmlStyles) { < foreach ($xmlStyles->cellXfs->xf as $xf) { < $numFmt = NumberFormat::FORMAT_GENERAL;> $this->styleReader->setNamespace($mainNS); > if (!$this->readDataOnly/* && $xmlStyles*/) { > foreach ($xfTags as $xfTag) { > $xf = self::getAttributes($xfTag); > $numFmt = null;if ($xf['numFmtId']) { if (isset($numFmts)) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); if (isset($tmpNumFmt['formatCode'])) { $numFmt = (string) $tmpNumFmt['formatCode']; } } // We shouldn't override any of the built-in MS Excel values (values below id 164) // But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used // So we make allowance for them rather than lose formatting masks< if ((int) $xf['numFmtId'] < 164 && < NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '') {> if ( > $numFmt === null && > (int) $xf['numFmtId'] < 164 && > NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '' > ) {$numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); } }< $quotePrefix = false; < if (isset($xf['quotePrefix'])) { < $quotePrefix = (bool) $xf['quotePrefix']; < }> $quotePrefix = (bool) ($xf['quotePrefix'] ?? false);$style = (object) [< 'numFmt' => $numFmt, < 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], < 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], < 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], < 'alignment' => $xf->alignment, < 'protection' => $xf->protection,> 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL, > 'font' => $fonts[(int) ($xf['fontId'])], > 'fill' => $fills[(int) ($xf['fillId'])], > 'border' => $borders[(int) ($xf['borderId'])], > 'alignment' => $xfTag->alignment, > 'protection' => $xfTag->protection,'quotePrefix' => $quotePrefix, ]; $styles[] = $style; // add style to cellXf collection $objStyle = new Style();< self::readStyle($objStyle, $style);> $this->styleReader->readStyle($objStyle, $style); > if ($addingFirstCellXf) { > $excel->removeCellXfByIndex(0); // remove the default style > $addingFirstCellXf = false; > }$excel->addCellXf($objStyle); }< foreach (isset($xmlStyles->cellStyleXfs->xf) ? $xmlStyles->cellStyleXfs->xf : [] as $xf) {> foreach ($cellXfTags as $xfTag) { > $xf = self::getAttributes($xfTag);$numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); if (isset($tmpNumFmt['formatCode'])) { $numFmt = (string) $tmpNumFmt['formatCode']; } elseif ((int) $xf['numFmtId'] < 165) { $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); } }> $quotePrefix = (bool) ($xf['quotePrefix'] ?? false); $cellStyle = (object) [ >'numFmt' => $numFmt,< 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], < 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], < 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], < 'alignment' => $xf->alignment, < 'protection' => $xf->protection,> 'font' => $fonts[(int) ($xf['fontId'])], > 'fill' => $fills[((int) $xf['fillId'])], > 'border' => $borders[(int) ($xf['borderId'])], > 'alignment' => $xfTag->alignment, > 'protection' => $xfTag->protection,'quotePrefix' => $quotePrefix, ]; $cellStyles[] = $cellStyle; // add style to cellStyleXf collection $objStyle = new Style();< self::readStyle($objStyle, $cellStyle);> $this->styleReader->readStyle($objStyle, $cellStyle); > if ($addingFirstCellStyleXf) { > $excel->removeCellStyleXfByIndex(0); // remove the default style > $addingFirstCellStyleXf = false; > }$excel->addCellStyleXf($objStyle); } }> $this->styleReader->setStyleXml($xmlStyles); > $this->styleReader->setNamespace($mainNS); $styleReader = new Styles($xmlStyles); > $this->styleReader->setStyleBaseData($theme, $styles, $cellStyles); $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles); > $dxfs = $this->styleReader->dxfs($this->readDataOnly); $dxfs = $styleReader->dxfs($this->readDataOnly); > $styles = $this->styleReader->styles();< $styleReader = new Styles($xmlStyles); < $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles); < $dxfs = $styleReader->dxfs($this->readDataOnly); < $styles = $styleReader->styles(); < < //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" < $xmlWorkbook = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS); > $xmlWorkbookNS = $this->loadZip($relTarget, $mainNS);< if ($xmlWorkbook->workbookPr) {> if ($xmlWorkbookNS->workbookPr) {< if (isset($xmlWorkbook->workbookPr['date1904'])) { < if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) {> $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr); > if (isset($attrs1904['date1904'])) { > if (self::boolean((string) $attrs1904['date1904'])) {Date::setExcelCalendar(Date::CALENDAR_MAC_1904); } } } // Set protection $this->readProtection($excel, $xmlWorkbook); $sheetId = 0; // keep track of new sheet id in final workbook $oldSheetId = -1; // keep track of old sheet id in final workbook $countSkippedSheets = 0; // keep track of number of skipped sheets $mapSheetId = []; // mapping of sheet ids from old to new $charts = $chartDetails = [];< if ($xmlWorkbook->sheets) {> if ($xmlWorkbookNS->sheets) {/** @var SimpleXMLElement $eleSheet */< foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {> foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) { > $eleSheetAttr = self::getAttributes($eleSheet);++$oldSheetId; // Check if sheet should be skipped< if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) {> if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) {++$countSkippedSheets; $mapSheetId[$oldSheetId] = null; continue; }> $sheetReferenceId = (string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id'); // Map old sheet id in original workbook to new sheet id. > if (isset($worksheets[$sheetReferenceId]) === false) { // They will differ if loadSheetsOnly() is being used > ++$countSkippedSheets; $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets; > $mapSheetId[$oldSheetId] = null; > // Load sheet > continue; $docSheet = $excel->createSheet(); > }// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet // references in formula cells... during the load, all formulae should be correct, // and we're simply bringing the worksheet name in line with the formula, not the // reverse< $docSheet->setTitle((string) $eleSheet['name'], false, false); < $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; < //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" < $xmlSheet = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $docSheet->setTitle((string) $eleSheetAttr['name'], false, false); > > $fileWorksheet = (string) $worksheets[$sheetReferenceId]; > $xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS); > $xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS);$sharedFormulas = [];< if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') { < $docSheet->setSheetState((string) $eleSheet['state']);> if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') { > $docSheet->setSheetState((string) $eleSheetAttr['state']);}< < if ($xmlSheet) { < if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { < $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet);> if ($xmlSheetNS) { > $xmlSheetMain = $xmlSheetNS->children($mainNS); > // Setting Conditional Styles adjusts selected cells, so we need to execute this > // before reading the sheet view data to get the actual selected cells > if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) { > (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); > } > if (!$this->readDataOnly && $xmlSheet->extLst) { > (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->loadFromExt($this->styleReader); > } > if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { > $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet);$sheetViews->load(); } $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet);< $sheetViewOptions->load($this->getReadDataOnly());> $sheetViewOptions->load($this->getReadDataOnly(), $this->styleReader);(new ColumnAndRowAttributes($docSheet, $xmlSheet)) ->load($this->getReadFilter(), $this->getReadDataOnly()); }< if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {> if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) {$cIndex = 1; // Cell Start from 1< foreach ($xmlSheet->sheetData->row as $row) {> foreach ($xmlSheetNS->sheetData->row as $row) {$rowIndex = 1; foreach ($row->c as $c) {< $r = (string) $c['r'];> $cAttr = self::getAttributes($c); > $r = (string) $cAttr['r'];if ($r == '') { $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex; }< $cellDataType = (string) $c['t'];> $cellDataType = (string) $cAttr['t'];$value = null; $calculatedValue = null; // Read cell? if ($this->getReadFilter() !== null) { $coordinates = Coordinate::coordinateFromString($r); if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {< $rowIndex += 1;> if (isset($cAttr->f)) { > $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); > } > ++$rowIndex;continue; } } // Read cell! switch ($cellDataType) { case 's': if ((string) $c->v != '') { $value = $sharedStrings[(int) ($c->v)]; if ($value instanceof RichText) { $value = clone $value; } } else { $value = ''; } break; case 'b': if (!isset($c->f)) {> if (isset($c->v)) {$value = self::castToBoolean($c); } else {> $value = null; // Formula > $cellDataType = DATATYPE::TYPE_NULL; $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean'); > } if (isset($c->f['t'])) { > } else {$att = $c->f; $docSheet->getCell($r)->setFormulaAttributes($att); } } break; case 'inlineStr': if (isset($c->f)) { $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); } else { $value = $this->parseRichText($c->is); } break; case 'e': if (!isset($c->f)) { $value = self::castToError($c); } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); } break; default: if (!isset($c->f)) { $value = self::castToString($c); } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');> if (isset($c->f['t'])) { } > $attributes = $c->f['t']; > $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]); break; > }} // read empty cells or the cells are not empty if ($this->readEmptyCells || ($value !== null && $value !== '')) { // Rich text? if ($value instanceof RichText && $this->readDataOnly) { $value = $value->getPlainText(); } $cell = $docSheet->getCell($r); // Assign value if ($cellDataType != '') {> // it is possible, that datatype is numeric but with an empty string, which result in an error $cell->setValueExplicit($value, $cellDataType); > if ($cellDataType === DataType::TYPE_NUMERIC && ($value === '' || $value === null)) { } else { > $cellDataType = DataType::TYPE_NULL; $cell->setValue($value); > } } > if ($cellDataType !== DataType::TYPE_NULL) {if ($calculatedValue !== null) {> }$cell->setCalculatedValue($calculatedValue); } // Style information?< if ($c['s'] && !$this->readDataOnly) {> if ($cAttr['s'] && !$this->readDataOnly) {// no style index means 0, it seems< $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ? < (int) ($c['s']) : 0); < }> $cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ? > (int) ($cAttr['s']) : 0);}< $rowIndex += 1;}< $cIndex += 1;> ++$rowIndex;}> ++$cIndex;}< < if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) { < (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();} $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells']; if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { foreach ($aKeys as $key) { $method = 'set' . ucfirst($key); $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key])); } }< if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { < $docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true); < if ($xmlSheet->protectedRanges->protectedRange) { < foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { < $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); < } < }> if ($xmlSheet) { > $this->readSheetProtection($docSheet, $xmlSheet);}< if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) { < (new AutoFilter($docSheet, $xmlSheet))->load();> if ($this->readDataOnly === false) { > $this->readAutoFilterTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip);} if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) { foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) { $mergeRef = (string) $mergeCell['ref']; if (strpos($mergeRef, ':') !== false) {< $docSheet->mergeCells((string) $mergeCell['ref']);> $docSheet->mergeCells((string) $mergeCell['ref'], Worksheet::MERGE_CELL_CONTENT_HIDE);} } } if ($xmlSheet && !$this->readDataOnly) { $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData); }> if ($xmlSheet !== false && isset($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri']) && ($xmlSheet->extLst->ext['uri'] == '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}')) { if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) { > // Create dataValidations node if does not exists, maybe is better inside the foreach ? (new DataValidations($docSheet, $xmlSheet))->load(); > if (!$xmlSheet->dataValidations) { } > $xmlSheet->addChild('dataValidations'); > } // unparsed sheet AlternateContent > if ($xmlSheet && !$this->readDataOnly) { > foreach ($xmlSheet->extLst->ext->children('x14', true)->dataValidations->dataValidation as $item) { $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); > $node = self::testSimpleXml($xmlSheet->dataValidations)->addChild('dataValidation'); if ($mc->AlternateContent) { > foreach ($item->attributes() ?? [] as $attr) { foreach ($mc->AlternateContent as $alternateContent) { > $node->addAttribute($attr->getName(), $attr); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); > } } > $node->addAttribute('sqref', $item->children('xm', true)->sqref); } > $node->addChild('formula1', $item->formula1->children('xm', true)->f); } > } > } // Add hyperlinks >< $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006');> $mc = $xmlSheet->children(Namespaces::COMPATIBILITY);$hyperlinkReader = new Hyperlinks($docSheet);> $alternateContent = self::testSimpleXml($alternateContent);// Locate hyperlink relations $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if ($zip->locateName($relationsFileName)) {< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorksheet = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, $relationsFileName) < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS);$hyperlinkReader->readHyperlinks($relsWorksheet); } // Loop through hyperlinks< if ($xmlSheet && $xmlSheet->hyperlinks) { < $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks);> if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) { > $hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks);} } // Add comments $comments = []; $vmlComments = []; if (!$this->readDataOnly) { // Locate comment relations< if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { < //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorksheet = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < foreach ($relsWorksheet->Relationship as $ele) { < if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') {> $commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; > if ($zip->locateName($commentRelations)) { > $relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS); > foreach ($relsWorksheet->Relationship as $elex) { > $ele = self::getAttributes($elex); > if ($ele['Type'] == Namespaces::COMMENTS) {$comments[(string) $ele['Id']] = (string) $ele['Target']; }< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {> if ($ele['Type'] == Namespaces::VML) {$vmlComments[(string) $ele['Id']] = (string) $ele['Target']; } } } // Loop through comments foreach ($comments as $relName => $relPath) { // Load comments file $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);< $commentsFile = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> // okay to ignore namespace - using xpath > $commentsFile = $this->loadZip($relPath, '');// Utility variables $authors = [];< < // Loop through authors < foreach ($commentsFile->authors->author as $author) {> $commentsFile->registerXpathNamespace('com', $mainNS); > $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author'); > foreach ($authorPath as $author) {$authors[] = (string) $author; } // Loop through contents< foreach ($commentsFile->commentList->comment as $comment) { < if (!empty($comment['authorId'])) { < $docSheet->getComment((string) $comment['ref'])->setAuthor($authors[(string) $comment['authorId']]);> $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment'); > foreach ($contentPath as $comment) { > $commentx = $comment->attributes(); > $commentModel = $docSheet->getComment((string) $commentx['ref']); > if (isset($commentx['authorId'])) { > $commentModel->setAuthor($authors[(int) $commentx['authorId']]);}< $docSheet->getComment((string) $comment['ref'])->setText($this->parseRichText($comment->text));> $commentModel->setText($this->parseRichText($comment->children($mainNS)->text));} } // later we will remove from it real vmlComments $unparsedVmlDrawings = $vmlComments; // Loop through VML comments foreach ($vmlComments as $relName => $relPath) { // Load VML comments file $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); try {< $vmlCommentsFile = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); < } catch (\Throwable $ex) {> // no namespace okay - processed with Xpath > $vmlCommentsFile = $this->loadZip($relPath, ''); > $vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML); > } catch (Throwable $ex) {//Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData continue; }< $shapes = $vmlCommentsFile->xpath('//v:shape');> // Locate VML drawings image relations > $drowingImages = []; > $VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels'; > if ($zip->locateName($VMLDrawingsRelations)) { > $relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS); > foreach ($relsVMLDrawing->Relationship as $elex) { > $ele = self::getAttributes($elex); > if ($ele['Type'] == Namespaces::IMAGE) { > $drowingImages[(string) $ele['Id']] = (string) $ele['Target']; > } > } > } > > $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape');foreach ($shapes as $shape) {< $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');> $shape->registerXPathNamespace('v', Namespaces::URN_VML);if (isset($shape['style'])) { $style = (string) $shape['style']; $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1)); $column = null; $row = null;> $fillImageRelId = null; > $fillImageTitle = '';$clientData = $shape->xpath('.//x:ClientData'); if (is_array($clientData) && !empty($clientData)) { $clientData = $clientData[0]; if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') { $temp = $clientData->xpath('.//x:Row'); if (is_array($temp)) { $row = $temp[0]; } $temp = $clientData->xpath('.//x:Column'); if (is_array($temp)) { $column = $temp[0]; } } }> $fillImageRelNode = $shape->xpath('.//v:fill/@o:relid'); if (($column !== null) && ($row !== null)) { > if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) { // Set comment properties > $fillImageRelNode = $fillImageRelNode[0]; $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1); > $comment->getFillColor()->setRGB($fillColor); > if (isset($fillImageRelNode['relid'])) { > $fillImageRelId = (string) $fillImageRelNode['relid']; // Parse style > } $styleArray = explode(';', str_replace(' ', '', $style)); > } foreach ($styleArray as $stylePair) { > $stylePair = explode(':', $stylePair); > $fillImageTitleNode = $shape->xpath('.//v:fill/@o:title'); > if (is_array($fillImageTitleNode) && !empty($fillImageTitleNode)) { if ($stylePair[0] == 'margin-left') { > $fillImageTitleNode = $fillImageTitleNode[0]; $comment->setMarginLeft($stylePair[1]); > } > if (isset($fillImageTitleNode['title'])) { if ($stylePair[0] == 'margin-top') { > $fillImageTitle = (string) $fillImageTitleNode['title']; $comment->setMarginTop($stylePair[1]); > } } > } if ($stylePair[0] == 'width') { >$comment->setWidth($stylePair[1]);> if (isset($drowingImages[$fillImageRelId])) { } > $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); if ($stylePair[0] == 'height') { > $objDrawing->setName($fillImageTitle); $comment->setHeight($stylePair[1]); > $imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]); } > $objDrawing->setPath( if ($stylePair[0] == 'visibility') { > 'zip://' . File::realpath($filename) . '#' . $imagePath, $comment->setVisible($stylePair[1] == 'visible'); > true, } > $zip } > ); > $comment->setBackgroundImage($objDrawing); unset($unparsedVmlDrawings[$relName]); > }} } } } // unparsed vmlDrawing if ($unparsedVmlDrawings) { foreach ($unparsedVmlDrawings as $rId => $relPath) { $rId = substr($rId, 3); // rIdXXX $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings']; $unparsedVmlDrawing[$rId] = []; $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath); $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath; $unparsedVmlDrawing[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath'])); unset($unparsedVmlDrawing); } } // Header/footer images< if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) {> if ($xmlSheet && $xmlSheet->legacyDrawingHF) {if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorksheet = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS);$vmlRelationship = ''; foreach ($relsWorksheet->Relationship as $ele) {< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {> if ($ele['Type'] == Namespaces::VML) {$vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); } } if ($vmlRelationship != '') { // Fetch linked images< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsVML = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS);$drawings = [];> if (isset($relsVML->Relationship)) {foreach ($relsVML->Relationship as $ele) {< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {> if ($ele['Type'] == Namespaces::IMAGE) {$drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); } }<> }// Fetch VML document< $vmlDrawing = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');> $vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, ''); > $vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML);$hfImages = [];< $shapes = $vmlDrawing->xpath('//v:shape');> $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape');foreach ($shapes as $idx => $shape) {< $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');> $shape->registerXPathNamespace('v', Namespaces::URN_VML);$imageData = $shape->xpath('//v:imagedata');< if (!$imageData) {> if (empty($imageData)) {continue; } $imageData = $imageData[$idx];< $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office');> $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE);$style = self::toCSSArray((string) $shape['style']); $hfImages[(string) $shape['id']] = new HeaderFooterDrawing(); if (isset($imageData['title'])) { $hfImages[(string) $shape['id']]->setName((string) $imageData['title']); }< $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($pFilename) . '#' . $drawings[(string) $imageData['relid']], false);> $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false);$hfImages[(string) $shape['id']]->setResizeProportional(false); $hfImages[(string) $shape['id']]->setWidth($style['width']); $hfImages[(string) $shape['id']]->setHeight($style['height']); if (isset($style['margin-left'])) { $hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']); } $hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']); $hfImages[(string) $shape['id']]->setResizeProportional(true); } $docSheet->getHeaderFooter()->setImages($hfImages); } } } } // TODO: Autoshapes from twoCellAnchors!< if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { < //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorksheet = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $drawingFilename = dirname("$dir/$fileWorksheet") > . '/_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 ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {> if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { > $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']); } }> } if ($xmlSheet->drawing && !$this->readDataOnly) { >$unparsedDrawings = [];> $fileDrawing = null;foreach ($xmlSheet->drawing as $drawing) {< $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id');> $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');$fileDrawing = $drawings[$drawingRelId];< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsDrawing = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels'; > $relsDrawing = $this->loadZipNoNamespace($drawingFilename, $xmlNamespaceBase); >$images = []; $hyperlinks = []; if ($relsDrawing && $relsDrawing->Relationship) { foreach ($relsDrawing->Relationship as $ele) {< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {> $eleType = (string) $ele['Type']; > if ($eleType === Namespaces::HYPERLINK) {$hyperlinks[(string) $ele['Id']] = (string) $ele['Target']; }< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {> if ($eleType === "$xmlNamespaceBase/image") {$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);< } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') {> } 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(), ]; } } } }< $xmlDrawing = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');> > $xmlDrawing = $this->loadZipNoNamespace($fileDrawing, ''); > $xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING);if ($xmlDrawingChildren->oneCellAnchor) { foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) { if ($oneCellAnchor->pic->blipFill) { /** @var SimpleXMLElement $blip */< $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;> $blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip;/** @var SimpleXMLElement $xfrm */< $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;> $xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm;/** @var SimpleXMLElement $outerShdw */< $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; < /** @var \SimpleXMLElement $hlinkClick */ < $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;> $outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; > /** @var SimpleXMLElement $hlinkClick */ > $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick;$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();< $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); < $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); < $objDrawing->setPath( < 'zip://' . File::realpath($pFilename) . '#' . < $images[(string) self::getArrayItem( < $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),> $objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name')); > $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr')); > $embedImageKey = (string) self::getArrayItem( > self::getAttributes($blip, $xmlNamespaceBase),'embed'< )],> ); > if (isset($images[$embedImageKey])) { > $objDrawing->setPath( > 'zip://' . File::realpath($filename) . '#' . > $images[$embedImageKey],false );< $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); < $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff));> } else { > $linkImageKey = (string) self::getArrayItem( > $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), > 'link' > ); > if (isset($images[$linkImageKey])) { > $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); > $objDrawing->setPath($url); > } > } > $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); > > $objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff));$objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false);< $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'))); < $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')));> $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx'))); > $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')));if ($xfrm) {< $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));> $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot')));} if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true);< $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); < $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); < $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); < $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); < $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr; < $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); < $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);> $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); > $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); > $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); > $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); > $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; > $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); > $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000);} $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet);< } else { < // ? Can charts be positioned with a oneCellAnchor ? < $coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);> } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) { > // Exported XLSX from Google Sheets positions charts with a oneCellAnchor > $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);$offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff);< $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')); < $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'));> $width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')); > $height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')); > > $graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; > /** @var SimpleXMLElement $chartRef */ > $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; > $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); > > $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ > 'fromCoordinate' => $coordinates, > 'fromOffsetX' => $offsetX, > 'fromOffsetY' => $offsetY, > 'width' => $width, > 'height' => $height, > 'worksheetTitle' => $docSheet->getTitle(), > 'oneCellAnchor' => true, > ];} } } if ($xmlDrawingChildren->twoCellAnchor) { foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) { if ($twoCellAnchor->pic->blipFill) {< $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; < $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; < $outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; < $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;> $blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; > $xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; > $outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; > $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick;$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();< $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); < $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); < $objDrawing->setPath( < 'zip://' . File::realpath($pFilename) . '#' . < $images[(string) self::getArrayItem( < $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),> /** @scrutinizer ignore-call */ > $editAs = $twoCellAnchor->attributes(); > if (isset($editAs, $editAs['editAs'])) { > $objDrawing->setEditAs($editAs['editAs']); > } > $objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name')); > $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr')); > $embedImageKey = (string) self::getArrayItem( > self::getAttributes($blip, $xmlNamespaceBase),'embed'< )],> ); > if (isset($images[$embedImageKey])) { > $objDrawing->setPath( > 'zip://' . File::realpath($filename) . '#' . > $images[$embedImageKey],false );< $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1));> } else { > $linkImageKey = (string) self::getArrayItem( > $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), > 'link' > ); > if (isset($images[$linkImageKey])) { > $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); > $objDrawing->setPath($url); > } > } > $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); >$objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff));> $objDrawing->setResizeProportional(false); > $objDrawing->setCoordinates2(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1)); > if ($xfrm) { > $objDrawing->setOffsetX2(Drawing::EMUToPixels($twoCellAnchor->to->colOff)); $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx'))); > $objDrawing->setOffsetY2(Drawing::EMUToPixels($twoCellAnchor->to->rowOff)); $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy'))); >< $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx'))); < $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy'))); < $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));> $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx'))); > $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy'))); > $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot')));$shadow = $objDrawing->getShadow(); $shadow->setVisible(true);< $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); < $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); < $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); < $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); < $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr; < $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); < $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);> $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); > $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); > $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); > $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); > $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; > $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); > $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000);} $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet); } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {< $fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);> $fromCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);$fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff); $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff);< $toCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1);> $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1);$toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff); $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);< $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic;> $graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;/** @var SimpleXMLElement $chartRef */< $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; < $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');> $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; > $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);$chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $fromCoordinate, 'fromOffsetX' => $fromOffsetX, 'fromOffsetY' => $fromOffsetY, 'toCoordinate' => $toCoordinate, 'toOffsetX' => $toOffsetX, 'toOffsetY' => $toOffsetY, 'worksheetTitle' => $docSheet->getTitle(), ]; } } }< if ($relsDrawing === false && $xmlDrawing->count() == 0) {> if ($xmlDrawingChildren->absoluteAnchor) { > foreach ($xmlDrawingChildren->absoluteAnchor as $absoluteAnchor) { > if (($this->includeCharts) && ($absoluteAnchor->graphicFrame)) { > $graphic = $absoluteAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; > /** @var SimpleXMLElement $chartRef */ > $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; > $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); > $width = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cx')[0]); > $height = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cy')[0]); > > $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ > 'fromCoordinate' => 'A1', > 'fromOffsetX' => 0, > 'fromOffsetY' => 0, > 'width' => $width, > 'height' => $height, > 'worksheetTitle' => $docSheet->getTitle(), > ]; > } > } > } > if (empty($relsDrawing) && $xmlDrawing->count() == 0) {// Save Drawing without rels and children as unparsed $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML(); } } // store original rId of drawing files $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; foreach ($relsWorksheet->Relationship as $ele) {< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {> if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") {$drawingRelId = (string) $ele['Id']; $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId; if (isset($unparsedDrawings[$drawingRelId])) { $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId]; } } } // unparsed drawing AlternateContent< $xmlAltDrawing = simplexml_load_string( < $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < )->children('http://schemas.openxmlformats.org/markup-compatibility/2006');> $xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY);if ($xmlAltDrawing->AlternateContent) { foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {> $alternateContent = self::testSimpleXml($alternateContent);$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); } } } }< $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); < $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);> $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); > $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);// Loop through definedNames if ($xmlWorkbook->definedNames) { foreach ($xmlWorkbook->definedNames->definedName as $definedName) { // Extract range $extractedRange = (string) $definedName; if (($spos = strpos($extractedRange, '!')) !== false) { $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos)); } else { $extractedRange = str_replace('$', '', $extractedRange); } // Valid range?< if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') {> if ($extractedRange == '') {continue; } // Some definedNames are only applicable if we are on the same sheet... if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) { // Switch on type switch ((string) $definedName['name']) { case '_xlnm._FilterDatabase': if ((string) $definedName['hidden'] !== '1') { $extractedRange = explode(',', $extractedRange); foreach ($extractedRange as $range) { $autoFilterRange = $range; if (strpos($autoFilterRange, ':') !== false) { $docSheet->getAutoFilter()->setRange($autoFilterRange); } } } break; case '_xlnm.Print_Titles': // Split $extractedRange $extractedRange = explode(',', $extractedRange); // Set print titles foreach ($extractedRange as $range) { $matches = []; $range = str_replace('$', '', $range); // check for repeating columns, e g. 'A:A' or 'A:D' if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) { $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]); } elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) { // check for repeating rows, e.g. '1:1' or '1:5' $docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]); } } break; case '_xlnm.Print_Area':< $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);> $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) ?: [];$newRangeSets = []; foreach ($rangeSets as $rangeSet) {< [$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);> [, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true); > if (empty($rangeSet)) { > continue; > }if (strpos($rangeSet, ':') === false) { $rangeSet = $rangeSet . ':' . $rangeSet; } $newRangeSets[] = str_replace('$', '', $rangeSet); }> if (count($newRangeSets) > 0) {$docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));> }break; default: break; } } } } // Next sheet id ++$sheetId; } // Loop through definedNames if ($xmlWorkbook->definedNames) { foreach ($xmlWorkbook->definedNames->definedName as $definedName) { // Extract range $extractedRange = (string) $definedName;< if (($spos = strpos($extractedRange, '!')) !== false) { < $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos)); < } else { < $extractedRange = str_replace('$', '', $extractedRange); < }// Valid range?< if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') {> if ($extractedRange == '') {continue; } // Some definedNames are only applicable if we are on the same sheet... if ((string) $definedName['localSheetId'] != '') { // Local defined name // Switch on type switch ((string) $definedName['name']) { case '_xlnm._FilterDatabase': case '_xlnm.Print_Titles': case '_xlnm.Print_Area': break; default: if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {< if (strpos((string) $definedName, '!') !== false) {$range = Worksheet::extractSheetTitle((string) $definedName, true);> $scope = $excel->getSheet($mapSheetId[(int) $definedName['localSheetId']]); $range[0] = str_replace("''", "'", $range[0]); > if (strpos((string) $definedName, '!') !== false) {$range[0] = str_replace("'", '', $range[0]);< if ($worksheet = $docSheet->getParent()->getSheetByName($range[0])) { < $extractedRange = str_replace('$', '', $range[1]); < $scope = $docSheet->getParent()->getSheet($mapSheetId[(int) $definedName['localSheetId']]); < $excel->addNamedRange(new NamedRange((string) $definedName['name'], $worksheet, $extractedRange, true, $scope));> if ($worksheet = $excel->getSheetByName($range[0])) { // @phpstan-ignore-line > $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope)); > } else { > $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope));}> } else { } > $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true));} break; } } elseif (!isset($definedName['localSheetId'])) {> $definedRange = (string) $definedName;// "Global" definedNames $locatedSheet = null;< $extractedSheetName = '';if (strpos((string) $definedName, '!') !== false) {> // Modify range, and extract the first worksheet reference // Extract sheet name > // Need to split on a comma or a space if not in quotes, and extract the first part. $extractedSheetName = Worksheet::extractSheetTitle((string) $definedName, true); > $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange);< $extractedSheetName = Worksheet::extractSheetTitle((string) $definedName, true); < $extractedSheetName = trim($extractedSheetName[0], "'");> [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); // @phpstan-ignore-line > $extractedSheetName = trim($extractedSheetName, "'");// Locate sheet $locatedSheet = $excel->getSheetByName($extractedSheetName);< < // Modify range < [$worksheetName, $extractedRange] = Worksheet::extractSheetTitle($extractedRange, true);}< if ($locatedSheet !== null) { < $excel->addNamedRange(new NamedRange((string) $definedName['name'], $locatedSheet, $extractedRange, false));> if ($locatedSheet === null && !DefinedName::testIfFormula($definedRange)) { > $definedRange = '#REF!';}> $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $locatedSheet, $definedRange, false));} } } }< if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) { < $workbookView = $xmlWorkbook->bookViews->workbookView; < < // active sheet index < $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index < < // keep active sheet index if sheet is still loaded, else first sheet is set as the active < if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { < $excel->setActiveSheetIndex($mapSheetId[$activeTab]); < } else { < if ($excel->getSheetCount() == 0) { < $excel->createSheet(); < } < $excel->setActiveSheetIndex(0); < } < < if (isset($workbookView['showHorizontalScroll'])) { < $showHorizontalScroll = (string) $workbookView['showHorizontalScroll']; < $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll)); < } < < if (isset($workbookView['showVerticalScroll'])) { < $showVerticalScroll = (string) $workbookView['showVerticalScroll']; < $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll)); < } < < if (isset($workbookView['showSheetTabs'])) { < $showSheetTabs = (string) $workbookView['showSheetTabs']; < $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs)); < } < < if (isset($workbookView['minimized'])) { < $minimized = (string) $workbookView['minimized']; < $excel->setMinimized($this->castXsdBooleanToBool($minimized)); < } < < if (isset($workbookView['autoFilterDateGrouping'])) { < $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping']; < $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping)); < } < < if (isset($workbookView['firstSheet'])) { < $firstSheet = (string) $workbookView['firstSheet']; < $excel->setFirstSheetIndex((int) $firstSheet); < } < < if (isset($workbookView['visibility'])) { < $visibility = (string) $workbookView['visibility']; < $excel->setVisibility($visibility); < } < < if (isset($workbookView['tabRatio'])) { < $tabRatio = (string) $workbookView['tabRatio']; < $excel->setTabRatio((int) $tabRatio); < } < }> (new WorkbookView($excel))->viewSettings($xmlWorkbook, $mainNS, $mapSheetId, $this->readDataOnly);break; } } if (!$this->readDataOnly) {< $contentTypes = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, '[Content_Types].xml') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $contentTypes = $this->loadZip('[Content_Types].xml');// Default content types foreach ($contentTypes->Default as $contentType) { switch ($contentType['ContentType']) { case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings': $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType']; break; } } // Override content types foreach ($contentTypes->Override as $contentType) { switch ($contentType['ContentType']) { case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml': if ($this->includeCharts) {< $chartEntryRef = ltrim($contentType['PartName'], '/'); < $chartElements = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, $chartEntryRef) < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml')); <> $chartEntryRef = ltrim((string) $contentType['PartName'], '/'); > $chartElements = $this->loadZip($chartEntryRef); > $chartReader = new Chart($chartNS, $drawingNS); > $objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml'));if (isset($charts[$chartEntryRef])) { $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id']; if (isset($chartDetails[$chartPositionRef])) {< $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart);> $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); // @phpstan-ignore-line$objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));> // For oneCellAnchor or absoluteAnchor positioned charts, $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); > // toCoordinate is not in the data. Does it need to be calculated? $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); > if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) { } > // twoCellAnchor}> } else { } > // oneCellAnchor or absoluteAnchor (e.g. Chart sheet) > $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); break; > $objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']); > if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) { // unparsed > $objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']); case 'application/vnd.ms-excel.controlproperties+xml': > } $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType']; > }break; } } } $excel->setUnparsedLoadedData($unparsedLoadedData); $zip->close(); return $excel; }< private static function readColor($color, $background = false) < { < if (isset($color['rgb'])) { < return (string) $color['rgb']; < } elseif (isset($color['indexed'])) { < return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); < } elseif (isset($color['theme'])) { < if (self::$theme !== null) { < $returnColour = self::$theme->getColourByIndex((int) $color['theme']); < if (isset($color['tint'])) { < $tintAdjust = (float) $color['tint']; < $returnColour = Color::changeBrightness($returnColour, $tintAdjust); < } < < return 'FF' . $returnColour; < } < } < < if ($background) { < return 'FFFFFFFF'; < } < < return 'FF000000'; < } </**< * @param Style $docStyle < * @param SimpleXMLElement|\stdClass $style < */ < private static function readStyle(Style $docStyle, $style) < { < $docStyle->getNumberFormat()->setFormatCode($style->numFmt); < < // font < if (isset($style->font)) { < $docStyle->getFont()->setName((string) $style->font->name['val']); < $docStyle->getFont()->setSize((string) $style->font->sz['val']); < if (isset($style->font->b)) { < $docStyle->getFont()->setBold(!isset($style->font->b['val']) || self::boolean((string) $style->font->b['val'])); < } < if (isset($style->font->i)) { < $docStyle->getFont()->setItalic(!isset($style->font->i['val']) || self::boolean((string) $style->font->i['val'])); < } < if (isset($style->font->strike)) { < $docStyle->getFont()->setStrikethrough(!isset($style->font->strike['val']) || self::boolean((string) $style->font->strike['val'])); < } < $docStyle->getFont()->getColor()->setARGB(self::readColor($style->font->color)); < < if (isset($style->font->u) && !isset($style->font->u['val'])) { < $docStyle->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); < } elseif (isset($style->font->u, $style->font->u['val'])) { < $docStyle->getFont()->setUnderline((string) $style->font->u['val']); < } < < if (isset($style->font->vertAlign, $style->font->vertAlign['val'])) { < $vertAlign = strtolower((string) $style->font->vertAlign['val']); < if ($vertAlign == 'superscript') { < $docStyle->getFont()->setSuperscript(true); < } < if ($vertAlign == 'subscript') { < $docStyle->getFont()->setSubscript(true); < } < } < } < < // fill < if (isset($style->fill)) { < if ($style->fill->gradientFill) { < /** @var SimpleXMLElement $gradientFill */ < $gradientFill = $style->fill->gradientFill[0]; < if (!empty($gradientFill['type'])) { < $docStyle->getFill()->setFillType((string) $gradientFill['type']); < } < $docStyle->getFill()->setRotation((float) ($gradientFill['degree'])); < $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); < $docStyle->getFill()->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); < $docStyle->getFill()->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); < } elseif ($style->fill->patternFill) { < $patternType = (string) $style->fill->patternFill['patternType'] != '' ? (string) $style->fill->patternFill['patternType'] : 'solid'; < $docStyle->getFill()->setFillType($patternType); < if ($style->fill->patternFill->fgColor) { < $docStyle->getFill()->getStartColor()->setARGB(self::readColor($style->fill->patternFill->fgColor, true)); < } else { < $docStyle->getFill()->getStartColor()->setARGB('FF000000'); < } < if ($style->fill->patternFill->bgColor) { < $docStyle->getFill()->getEndColor()->setARGB(self::readColor($style->fill->patternFill->bgColor, true)); < } < } < } < < // border < if (isset($style->border)) { < $diagonalUp = self::boolean((string) $style->border['diagonalUp']); < $diagonalDown = self::boolean((string) $style->border['diagonalDown']); < if (!$diagonalUp && !$diagonalDown) { < $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); < } elseif ($diagonalUp && !$diagonalDown) { < $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP); < } elseif (!$diagonalUp && $diagonalDown) { < $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); < } else { < $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH); < } < self::readBorder($docStyle->getBorders()->getLeft(), $style->border->left); < self::readBorder($docStyle->getBorders()->getRight(), $style->border->right); < self::readBorder($docStyle->getBorders()->getTop(), $style->border->top); < self::readBorder($docStyle->getBorders()->getBottom(), $style->border->bottom); < self::readBorder($docStyle->getBorders()->getDiagonal(), $style->border->diagonal); < } < < // alignment < if (isset($style->alignment)) { < $docStyle->getAlignment()->setHorizontal((string) $style->alignment['horizontal']); < $docStyle->getAlignment()->setVertical((string) $style->alignment['vertical']); < < $textRotation = 0; < if ((int) $style->alignment['textRotation'] <= 90) { < $textRotation = (int) $style->alignment['textRotation']; < } elseif ((int) $style->alignment['textRotation'] > 90) { < $textRotation = 90 - (int) $style->alignment['textRotation']; < } < < $docStyle->getAlignment()->setTextRotation((int) $textRotation); < $docStyle->getAlignment()->setWrapText(self::boolean((string) $style->alignment['wrapText'])); < $docStyle->getAlignment()->setShrinkToFit(self::boolean((string) $style->alignment['shrinkToFit'])); < $docStyle->getAlignment()->setIndent((int) ((string) $style->alignment['indent']) > 0 ? (int) ((string) $style->alignment['indent']) : 0); < $docStyle->getAlignment()->setReadOrder((int) ((string) $style->alignment['readingOrder']) > 0 ? (int) ((string) $style->alignment['readingOrder']) : 0); < } < < // protection < if (isset($style->protection)) { < if (isset($style->protection['locked'])) { < if (self::boolean((string) $style->protection['locked'])) { < $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED); < } else { < $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED); < } < } < < if (isset($style->protection['hidden'])) { < if (self::boolean((string) $style->protection['hidden'])) { < $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED); < } else { < $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED); < } < } < } < < // top-level style settings < if (isset($style->quotePrefix)) { < $docStyle->setQuotePrefix($style->quotePrefix); < } < } < < /** < * @param Border $docBorder < * @param SimpleXMLElement $eleBorder < */ < private static function readBorder(Border $docBorder, $eleBorder) < { < if (isset($eleBorder['style'])) { < $docBorder->setBorderStyle((string) $eleBorder['style']); < } < if (isset($eleBorder->color)) { < $docBorder->getColor()->setARGB(self::readColor($eleBorder->color)); < } < } < < /** < * @param SimpleXMLElement | null $is < ** @return RichText */< private function parseRichText($is)> private function parseRichText(?SimpleXMLElement $is){ $value = new RichText(); if (isset($is->t)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t)); } else { if (is_object($is->r)) {> /** @var SimpleXMLElement $run */foreach ($is->r as $run) { if (!isset($run->rPr)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); } else { $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t));> $objFont = $objText->getFont() ?? new StyleFont();< if (isset($run->rPr->rFont['val'])) { < $objText->getFont()->setName((string) $run->rPr->rFont['val']);> if (isset($run->rPr->rFont)) { > $attr = $run->rPr->rFont->attributes(); > if (isset($attr['val'])) { > $objFont->setName((string) $attr['val']);}< if (isset($run->rPr->sz['val'])) { < $objText->getFont()->setSize((float) $run->rPr->sz['val']);}< if (isset($run->rPr->color)) { < $objText->getFont()->setColor(new Color(self::readColor($run->rPr->color)));> if (isset($run->rPr->sz)) { > $attr = $run->rPr->sz->attributes(); > if (isset($attr['val'])) { > $objFont->setSize((float) $attr['val']);}< if ((isset($run->rPr->b['val']) && self::boolean((string) $run->rPr->b['val'])) || < (isset($run->rPr->b) && !isset($run->rPr->b['val']))) { < $objText->getFont()->setBold(true); < } < if ((isset($run->rPr->i['val']) && self::boolean((string) $run->rPr->i['val'])) || < (isset($run->rPr->i) && !isset($run->rPr->i['val']))) { < $objText->getFont()->setItalic(true);}< if (isset($run->rPr->vertAlign, $run->rPr->vertAlign['val'])) { < $vertAlign = strtolower((string) $run->rPr->vertAlign['val']);> if (isset($run->rPr->color)) { > $objFont->setColor(new Color($this->styleReader->readColor($run->rPr->color))); > } > if (isset($run->rPr->b)) { > $attr = $run->rPr->b->attributes(); > if ( > (isset($attr['val']) && self::boolean((string) $attr['val'])) || > (!isset($attr['val'])) > ) { > $objFont->setBold(true); > } > } > if (isset($run->rPr->i)) { > $attr = $run->rPr->i->attributes(); > if ( > (isset($attr['val']) && self::boolean((string) $attr['val'])) || > (!isset($attr['val'])) > ) { > $objFont->setItalic(true); > } > } > if (isset($run->rPr->vertAlign)) { > $attr = $run->rPr->vertAlign->attributes(); > if (isset($attr['val'])) { > $vertAlign = strtolower((string) $attr['val']);if ($vertAlign == 'superscript') {< $objText->getFont()->setSuperscript(true);> $objFont->setSuperscript(true);} if ($vertAlign == 'subscript') {< $objText->getFont()->setSubscript(true);> $objFont->setSubscript(true);} }< if (isset($run->rPr->u) && !isset($run->rPr->u['val'])) { < $objText->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); < } elseif (isset($run->rPr->u, $run->rPr->u['val'])) { < $objText->getFont()->setUnderline((string) $run->rPr->u['val']);}< if ((isset($run->rPr->strike['val']) && self::boolean((string) $run->rPr->strike['val'])) || < (isset($run->rPr->strike) && !isset($run->rPr->strike['val']))) { < $objText->getFont()->setStrikethrough(true);> if (isset($run->rPr->u)) { > $attr = $run->rPr->u->attributes(); > if (!isset($attr['val'])) { > $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); > } else { > $objFont->setUnderline((string) $attr['val']); > } > } > if (isset($run->rPr->strike)) { > $attr = $run->rPr->strike->attributes(); > if ( > (isset($attr['val']) && self::boolean((string) $attr['val'])) || > (!isset($attr['val'])) > ) { > $objFont->setStrikethrough(true); > }} } } } } return $value; }< /** < * @param Spreadsheet $excel < * @param mixed $customUITarget < * @param mixed $zip < */ < private function readRibbon(Spreadsheet $excel, $customUITarget, $zip)> private function readRibbon(Spreadsheet $excel, string $customUITarget, ZipArchive $zip): void{ $baseDir = dirname($customUITarget); $nameCustomUI = basename($customUITarget); // get the xml file (ribbon) $localRibbon = $this->getFromZipArchive($zip, $customUITarget); $customUIImagesNames = []; $customUIImagesBinaries = []; // something like customUI/_rels/customUI.xml.rels $pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels'; $dataRels = $this->getFromZipArchive($zip, $pathRels); if ($dataRels) { // exists and not empty if the ribbon have some pictures (other than internal MSO) $UIRels = simplexml_load_string( $this->securityScanner->scan($dataRels), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); if (false !== $UIRels) { // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image foreach ($UIRels->Relationship as $ele) {< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {> if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/image') {// an image ? $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target']; $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']); } } } } if ($localRibbon) { $excel->setRibbonXMLData($customUITarget, $localRibbon); if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) { $excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries); } else { $excel->setRibbonBinObjects(null, null); } } else { $excel->setRibbonXMLData(null, null); $excel->setRibbonBinObjects(null, null); } }> /** private static function getArrayItem($array, $key = 0) > * @param null|array|bool|SimpleXMLElement $array { > * @param int|string $key return $array[$key] ?? null; > * } > * @return mixed > */< return $array[$key] ?? null;> return ($array === null || is_bool($array)) ? null : ($array[$key] ?? null);< private static function dirAdd($base, $add)> /** > * @param null|SimpleXMLElement|string $base > * @param null|SimpleXMLElement|string $add > */ > private static function dirAdd($base, $add): string< return preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");> $base = (string) $base; > $add = (string) $add; > > return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");}< private static function toCSSArray($style)> private static function toCSSArray(string $style): array{< $style = trim(str_replace(["\r", "\n"], '', $style), ';');> $style = self::stripWhiteSpaceFromStyleString($style);$temp = explode(';', $style); $style = []; foreach ($temp as $item) { $item = explode(':', $item); if (strpos($item[1], 'px') !== false) { $item[1] = str_replace('px', '', $item[1]); } if (strpos($item[1], 'pt') !== false) { $item[1] = str_replace('pt', '', $item[1]);< $item[1] = Font::fontSizeToPixels($item[1]);> $item[1] = (string) Font::fontSizeToPixels((int) $item[1]);} if (strpos($item[1], 'in') !== false) { $item[1] = str_replace('in', '', $item[1]);< $item[1] = Font::inchSizeToPixels($item[1]);> $item[1] = (string) Font::inchSizeToPixels((int) $item[1]);} if (strpos($item[1], 'cm') !== false) { $item[1] = str_replace('cm', '', $item[1]);< $item[1] = Font::centimeterSizeToPixels($item[1]);> $item[1] = (string) Font::centimeterSizeToPixels((int) $item[1]);} $style[$item[0]] = $item[1]; } return $style; }< private static function boolean($value)> public static function stripWhiteSpaceFromStyleString(string $string): string > { > return trim(str_replace(["\r", "\n", ' '], '', $string), ';'); > } > > /** > * @param mixed $value > */ > private static function boolean($value): bool{ if (is_object($value)) { $value = (string) $value; } if (is_numeric($value)) { return (bool) $value; } return $value === 'true' || $value === 'TRUE'; } /**< * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing < * @param \SimpleXMLElement $cellAnchor* @param array $hyperlinks */< private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks)> private function readHyperLinkDrawing(\PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing, SimpleXMLElement $cellAnchor, $hyperlinks): void{< $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;> $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick;if ($hlinkClick->count() === 0) { return; }< $hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id'];> $hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id'];$hyperlink = new Hyperlink( $hyperlinks[$hlinkId],< (string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')> (string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name')); $objDrawing->setHyperlink($hyperlink); }< private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook)> private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void{ if (!$xmlWorkbook->workbookProtection) { return; }< if ($xmlWorkbook->workbookProtection['lockRevision']) { < $excel->getSecurity()->setLockRevision((bool) $xmlWorkbook->workbookProtection['lockRevision']); < }> $excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision')); > $excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure')); > $excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows'));< if ($xmlWorkbook->workbookProtection['lockStructure']) { < $excel->getSecurity()->setLockStructure((bool) $xmlWorkbook->workbookProtection['lockStructure']);> if ($xmlWorkbook->workbookProtection['revisionsPassword']) { > $excel->getSecurity()->setRevisionsPassword( > (string) $xmlWorkbook->workbookProtection['revisionsPassword'], > true > );}< if ($xmlWorkbook->workbookProtection['lockWindows']) { < $excel->getSecurity()->setLockWindows((bool) $xmlWorkbook->workbookProtection['lockWindows']);> if ($xmlWorkbook->workbookProtection['workbookPassword']) { > $excel->getSecurity()->setWorkbookPassword( > (string) $xmlWorkbook->workbookProtection['workbookPassword'], > true > );}< < if ($xmlWorkbook->workbookProtection['revisionsPassword']) { < $excel->getSecurity()->setRevisionsPassword((string) $xmlWorkbook->workbookProtection['revisionsPassword'], true);}< if ($xmlWorkbook->workbookProtection['workbookPassword']) { < $excel->getSecurity()->setWorkbookPassword((string) $xmlWorkbook->workbookProtection['workbookPassword'], true);> private static function getLockValue(SimpleXmlElement $protection, string $key): ?bool > { > $returnValue = null; > $protectKey = $protection[$key]; > if (!empty($protectKey)) { > $protectKey = (string) $protectKey; > $returnValue = $protectKey !== 'false' && (bool) $protectKey;}> } > return $returnValue;< private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData)> private function readFormControlProperties(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void{> $zip = $this->zip;if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; }< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorksheet = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; > $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS);$ctrlProps = []; foreach ($relsWorksheet->Relationship as $ele) {< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') {> if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') {$ctrlProps[(string) $ele['Id']] = $ele; } } $unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps']; foreach ($ctrlProps as $rId => $ctrlProp) { $rId = substr($rId, 3); // rIdXXX $unparsedCtrlProps[$rId] = []; $unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']); $unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target']; $unparsedCtrlProps[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath'])); } unset($unparsedCtrlProps); }< private function readPrinterSettings(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData)> private function readPrinterSettings(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void{> $zip = $this->zip;if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; }< //~ http://schemas.openxmlformats.org/package/2006/relationships" < $relsWorksheet = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < );> $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; > $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS);$sheetPrinterSettings = []; foreach ($relsWorksheet->Relationship as $ele) {< if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') {> if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/printerSettings') {$sheetPrinterSettings[(string) $ele['Id']] = $ele; } } $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings']; foreach ($sheetPrinterSettings as $rId => $printerSettings) {< $rId = substr($rId, 3); // rIdXXX> $rId = substr($rId, 3) . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing$unparsedPrinterSettings[$rId] = []; $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']); $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target']; $unparsedPrinterSettings[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath'])); } unset($unparsedPrinterSettings); }< /** < * Convert an 'xsd:boolean' XML value to a PHP boolean value. < * A valid 'xsd:boolean' XML value can be one of the following < * four values: 'true', 'false', '1', '0'. It is case sensitive. < * < * Note that just doing '(bool) $xsdBoolean' is not safe, < * since '(bool) "false"' returns true. < * < * @see https://www.w3.org/TR/xmlschema11-2/#boolean < * < * @param string $xsdBoolean An XML string value of type 'xsd:boolean' < * < * @return bool Boolean value < */ < private function castXsdBooleanToBool($xsdBoolean) < { < if ($xsdBoolean === 'false') { < return false; < } < < return (bool) $xsdBoolean; < } < < /** < * @param ZipArchive $zip Opened zip archive < * < * @return string basename of the used excel workbook < */ < private function getWorkbookBaseName(ZipArchive $zip)> private function getWorkbookBaseName(): array{ $workbookBasename = '';> $xmlNamespaceBase = '';// check if it is an OOXML archive< $rels = simplexml_load_string( < $this->securityScanner->scan( < $this->getFromZipArchive($zip, '_rels/.rels') < ), < 'SimpleXMLElement', < Settings::getLibXmlLoaderOptions() < ); < if ($rels !== false) { < foreach ($rels->Relationship as $rel) { < switch ($rel['Type']) { < case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': < $basename = basename($rel['Target']);> $rels = $this->loadZip(self::INITIAL_FILE); > foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) { > $rel = self::getAttributes($rel); > $type = (string) $rel['Type']; > switch ($type) { > case Namespaces::OFFICE_DOCUMENT: > case Namespaces::PURL_OFFICE_DOCUMENT: > $basename = basename((string) $rel['Target']); > $xmlNamespaceBase = dirname($type);if (preg_match('/workbook.*\.xml/', $basename)) { $workbookBasename = $basename; } break; } }> } > return [$workbookBasename, $xmlNamespaceBase]; > } return $workbookBasename; > } > private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void } > { > if ($this->readDataOnly || !$xmlSheet->sheetProtection) { > return; > } > > $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName']; > $protection = $docSheet->getProtection(); > $protection->setAlgorithm($algorithmName); > > if ($algorithmName) { > $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true); > $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']); > $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']); > } else { > $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true); > } > > if ($xmlSheet->protectedRanges->protectedRange) { > foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { > $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); > } > } > } > > private function readAutoFilterTables( > SimpleXMLElement $xmlSheet, > Worksheet $docSheet, > string $dir, > string $fileWorksheet, > ZipArchive $zip > ): void { > if ($xmlSheet && $xmlSheet->autoFilter) { > // In older files, autofilter structure is defined in the worksheet file > (new AutoFilter($docSheet, $xmlSheet))->load(); > } elseif ($xmlSheet && $xmlSheet->tableParts && (int) $xmlSheet->tableParts['count'] > 0) { > // But for Office365, MS decided to make it all just a bit more complicated > $this->readAutoFilterTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet); > } > } > > private function readAutoFilterTablesInTablesFile( > SimpleXMLElement $xmlSheet, > string $dir, > string $fileWorksheet, > ZipArchive $zip, > Worksheet $docSheet > ): void { > foreach ($xmlSheet->tableParts->tablePart as $tablePart) { > $relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT); > $tablePartRel = (string) $relation['id']; > $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; > > if ($zip->locateName($relationsFileName)) { > $relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); > foreach ($relsTableReferences->Relationship as $relationship) { > $relationshipAttributes = self::getAttributes($relationship, ''); > > if ((string) $relationshipAttributes['Id'] === $tablePartRel) { > $relationshipFileName = (string) $relationshipAttributes['Target']; > $relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName; > $relationshipFilePath = File::realpath($relationshipFilePath); > > if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) { > $autoFilter = $this->loadZip($relationshipFilePath); > (new AutoFilter($docSheet, $autoFilter))->load(); > } > } > } > } > } > } > > private static function extractStyles(?SimpleXMLElement $sxml, string $node1, string $node2): array > { > $array = []; > if ($sxml && $sxml->{$node1}->{$node2}) { > foreach ($sxml->{$node1}->{$node2} as $node) { > $array[] = $node; > } > } > > return $array; > } > > private static function extractPalette(?SimpleXMLElement $sxml): array > { > $array = []; > if ($sxml && $sxml->colors->indexedColors) { > foreach ($sxml->colors->indexedColors->rgbColor as $node) { > if ($node !== null) { > $attr = $node->attributes(); > if (isset($attr['rgb'])) { > $array[] = (string) $attr['rgb']; > } > } > }< return $workbookBasename;> return (count($array) === 64) ? $array : [];