From a945fca764a0cafbf85734c9ef059353190f304b Mon Sep 17 00:00:00 2001 From: schrom01 Date: Tue, 14 Feb 2023 19:56:51 +0100 Subject: [PATCH] php functions to Store data --- data/data.xlsx | Bin 0 -> 9745 bytes dataCollector/dataCollector.php | 44 + index.html | 281 +++--- js/functions.js | 43 +- spreadSheetReader/SimpleXLSX.php | 1220 +++++++++++++++++++++++++ spreadSheetReader/SimpleXLSXGen.php | 1015 ++++++++++++++++++++ spreadSheetReader/read_write_xlsx.php | 28 + 7 files changed, 2475 insertions(+), 156 deletions(-) create mode 100644 data/data.xlsx create mode 100644 dataCollector/dataCollector.php create mode 100644 spreadSheetReader/SimpleXLSX.php create mode 100644 spreadSheetReader/SimpleXLSXGen.php create mode 100644 spreadSheetReader/read_write_xlsx.php diff --git a/data/data.xlsx b/data/data.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a01bc61c9bf3cca833b52b129178cc0df7e9ea80 GIT binary patch literal 9745 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 literal 0 HcmV?d00001 diff --git a/dataCollector/dataCollector.php b/dataCollector/dataCollector.php new file mode 100644 index 0000000..3bdd00a --- /dev/null +++ b/dataCollector/dataCollector.php @@ -0,0 +1,44 @@ + id, + $object -> lastSave, + $fullJSON, + $object -> timeWatchM, + $object -> timeWatchW, + $object -> timeWatchD, + $object -> countWatchM, + $object -> countWatchW, + $object -> countWatchD]; +} + +function findRowIndex($id, $data) { + $rowCounter = -1; + foreach ($data as $row) { + $rowCounter ++; + if($row[0] == $id) { + return $rowCounter; + } + } + return $rowCounter + 1; +} + +$entityBody = '{"lastSave": 0, "timeWatchM": 23, "timeWatchW": 89, "timeWatchD": 34, "countWatchM": 65, "countWatchW": 56, "countWatchD": 9, "id":"test","watches":[{"actions":[]},{"actions":[]},{"actions":[]}]}'; +if ($_SERVER['REQUEST_METHOD'] === 'PUT') { + $entityBody = file_get_contents('php://input'); +} + +$entityBodyObject = json_decode($entityBody); + + +require $_SERVER['DOCUMENT_ROOT'] . '/spreadSheetReader/read_write_xlsx.php'; +if(property_exists($entityBodyObject, "id")) { + $data = read_xls_file($fileName); + $rowIndex = findRowIndex($entityBodyObject->id, $data); + $data[$rowIndex] = objectToArray($entityBodyObject, $entityBody); + write_xlsxFile($fileName, $data); +} + + + + diff --git a/index.html b/index.html index 0f1ba5d..ebb39a3 100644 --- a/index.html +++ b/index.html @@ -1,142 +1,141 @@ - - - - - - Genderwatch-protocol - - - -
-
-

Genderwatch-protocol

-

Aktuelles Thema

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- M - - W - - D -
- 0 - - 0 - - 0 -
- 0 - - 0 - - 0 -
- - - - - -
- -
- -
- - -
- -
- - -

Abgeschlossene Themen

- - - - - - - - - - - - - - - - -
- Thema - - M - - W - - D -
- -
-
- - - - - + + + + + + Genderwatch-protocol + + + +
+
+

Genderwatch-protocol

+

Aktuelles Thema

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ M + + W + + D +
+ 0 + + 0 + + 0 +
+ 0 + + 0 + + 0 +
+ + + + + +
+ +
+ +
+ + +
+ +
+ + +

Abgeschlossene Themen

+ + + + + + + + + + + + + + + + +
+ Thema + + M + + W + + D +
+ +
+
+ + + + + \ No newline at end of file diff --git a/js/functions.js b/js/functions.js index f97adc9..51ff018 100644 --- a/js/functions.js +++ b/js/functions.js @@ -1,6 +1,12 @@ +function initializeNewTopic() { + actualTopic = {"name": "", "id": randomId()} + reset() + document.getElementById("topicInput").value = actualTopic.name +} + function reset() { actualTopic.watches = [{"actions": []}, {"actions": []}, {"actions": []}] - saveActualTopicToLocalStorage() + saveActualTopic() } function resetButton() { @@ -12,7 +18,7 @@ function resetButton() { function buttonDeleteAllClosedTopics() { if(confirm("Sollen alle abgeschlossenen Themen gelöscht werden?")) { closedTopics = [] - saveClosedTopicsToLocalStorage() + saveClosedTopics() refreshClosedTopics() } } @@ -43,17 +49,14 @@ function closeTopic() { } actualTopic.name = name closedTopics.push(actualTopic) - actualTopic = {"name": ""} - reset() - document.getElementById("topicInput").value = actualTopic.name - saveActualTopicToLocalStorage() - saveClosedTopicsToLocalStorage() + saveClosedTopics() refreshClosedTopics() + initializeNewTopic() } function deleteTopic(topicIndex) { closedTopics = closedTopics.slice(0, topicIndex).concat(closedTopics.slice(topicIndex+1)) - saveClosedTopicsToLocalStorage() + saveClosedTopics() refreshClosedTopics() } @@ -64,14 +67,14 @@ function reopenTopic(topicIndex) { actualTopic = closedTopics[topicIndex] document.getElementById("topicInput").value = actualTopic.name deleteTopic(topicIndex) - saveClosedTopicsToLocalStorage() - saveActualTopicToLocalStorage() + saveClosedTopics() + saveActualTopic() } function start(watchIndex) { stop() actualTopic.watches[watchIndex].actions.push({"type": "start", "time": new Date().getTime()}) - saveActualTopicToLocalStorage() + saveActualTopic() } function stop() { @@ -82,7 +85,7 @@ function stop() { } } }); - saveActualTopicToLocalStorage() + saveActualTopic() } function calcPercForWatch(watchIndex, topic) { @@ -181,14 +184,18 @@ function refreshClosedTopics() { function saveActualTopicName() { actualTopic.name = document.getElementById("topicInput").value - saveActualTopicToLocalStorage() + saveActualTopic() } -function saveActualTopicToLocalStorage() { + + +function saveActualTopic() { + //save it to LocalStorage localStorage.setItem("actualTopic", JSON.stringify(actualTopic)) } -function saveClosedTopicsToLocalStorage() { +function saveClosedTopics() { + //save it to LocalStorage localStorage.setItem("closedTopics", JSON.stringify(closedTopics)) } @@ -204,4 +211,10 @@ function elt (type, attrs, ...children) { else node.appendChild(document.createTextNode(child)) } return node +} + +// random.ts +function randomId() { + const uint32 = window.crypto.getRandomValues(new Uint32Array(1))[0]; + return uint32.toString(16); } \ No newline at end of file diff --git a/spreadSheetReader/SimpleXLSX.php b/spreadSheetReader/SimpleXLSX.php new file mode 100644 index 0000000..df426d4 --- /dev/null +++ b/spreadSheetReader/SimpleXLSX.php @@ -0,0 +1,1220 @@ +rows() as $r) { + * print_r( $r ); + * } + * } else { + * echo SimpleXLSX::parseError(); + * } + * + * Example 2: html table + * if ( $xlsx = SimpleXLSX::parse('book.xlsx') ) { + * echo $xlsx->toHTML(); + * } else { + * echo SimpleXLSX::parseError(); + * } + * + * Example 3: rowsEx + * $xlsx = SimpleXLSX::parse('book.xlsx'); + * foreach ( $xlsx->rowsEx() as $r ) { + * print_r( $r ); + * } + * + * Example 4: select worksheet + * $xlsx = SimpleXLSX::parse('book.xlsx'); + * foreach( $xlsx->rows(1) as $r ) { // second worksheet + * print_t( $r ); + * } + * + * Example 5: IDs and worksheet names + * $xlsx = SimpleXLSX::parse('book.xlsx'); + * print_r( $xlsx->sheetNames() ); // array( 0 => 'Sheet 1', 1 => 'Catalog' ); + * + * Example 6: get sheet name by index + * $xlsx = SimpleXLSX::parse('book.xlsx'); + * echo 'Sheet Name 2 = '.$xlsx->sheetName(1); + * + * Example 7: getCell (very slow) + * echo $xlsx->getCell(1,'D12'); // reads D12 cell from second sheet + * + * Example 8: read data + * if ( $xlsx = SimpleXLSX::parseData( file_get_contents('http://www.example.com/example.xlsx') ) ) { + * $dim = $xlsx->dimension(1); + * $num_cols = $dim[0]; + * $num_rows = $dim[1]; + * echo $xlsx->sheetName(1).':'.$num_cols.'x'.$num_rows; + * } else { + * echo SimpleXLSX::parseError(); + * } + * + * Example 9: old style + * $xlsx = new SimpleXLSX('book.xlsx'); + * if ( $xlsx->success() ) { + * print_r( $xlsx->rows() ); + * } else { + * echo 'xlsx error: '.$xlsx->error(); + * } + */ +class SimpleXLSX +{ + // Don't remove this string! Created by Sergey Shuchkin sergey.shuchkin@gmail.com + public static $CF = [ // Cell formats + 0 => 'General', + 1 => '0', + 2 => '0.00', + 3 => '#,##0', + 4 => '#,##0.00', + 9 => '0%', + 10 => '0.00%', + 11 => '0.00E+00', + 12 => '# ?/?', + 13 => '# ??/??', + 14 => 'mm-dd-yy', + 15 => 'd-mmm-yy', + 16 => 'd-mmm', + 17 => 'mmm-yy', + 18 => 'h:mm AM/PM', + 19 => 'h:mm:ss AM/PM', + 20 => 'h:mm', + 21 => 'h:mm:ss', + 22 => 'm/d/yy h:mm', + + 37 => '#,##0 ;(#,##0)', + 38 => '#,##0 ;[Red](#,##0)', + 39 => '#,##0.00;(#,##0.00)', + 40 => '#,##0.00;[Red](#,##0.00)', + + 44 => '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)', + 45 => 'mm:ss', + 46 => '[h]:mm:ss', + 47 => 'mmss.0', + 48 => '##0.0E+0', + 49 => '@', + + 27 => '[$-404]e/m/d', + 30 => 'm/d/yy', + 36 => '[$-404]e/m/d', + 50 => '[$-404]e/m/d', + 57 => '[$-404]e/m/d', + + 59 => 't0', + 60 => 't0.00', + 61 => 't#,##0', + 62 => 't#,##0.00', + 67 => 't0%', + 68 => 't0.00%', + 69 => 't# ?/?', + 70 => 't# ??/??', + ]; + public $nf = []; // number formats + public $cellFormats = []; // cellXfs + public $datetimeFormat = 'Y-m-d H:i:s'; + public $debug; + public $activeSheet = 0; + public $rowsExReader; + + /* @var SimpleXMLElement[] $sheets */ + protected $sheets; + protected $sheetNames = []; + protected $sheetFiles = []; + // scheme + public $styles; + protected $hyperlinks; + /* @var array[] $package */ + protected $package; + protected $sharedstrings; + protected $date1904 = 0; + + + /* + private $date_formats = array( + 0xe => "d/m/Y", + 0xf => "d-M-Y", + 0x10 => "d-M", + 0x11 => "M-Y", + 0x12 => "h:i a", + 0x13 => "h:i:s a", + 0x14 => "H:i", + 0x15 => "H:i:s", + 0x16 => "d/m/Y H:i", + 0x2d => "i:s", + 0x2e => "H:i:s", + 0x2f => "i:s.S" + ); + private $number_formats = array( + 0x1 => "%1.0f", // "0" + 0x2 => "%1.2f", // "0.00", + 0x3 => "%1.0f", //"#,##0", + 0x4 => "%1.2f", //"#,##0.00", + 0x5 => "%1.0f", //"$#,##0;($#,##0)", + 0x6 => '$%1.0f', //"$#,##0;($#,##0)", + 0x7 => '$%1.2f', //"$#,##0.00;($#,##0.00)", + 0x8 => '$%1.2f', //"$#,##0.00;($#,##0.00)", + 0x9 => '%1.0f%%', //"0%" + 0xa => '%1.2f%%', //"0.00%" + 0xb => '%1.2f', //"0.00E00", + 0x25 => '%1.0f', //"#,##0;(#,##0)", + 0x26 => '%1.0f', //"#,##0;(#,##0)", + 0x27 => '%1.2f', //"#,##0.00;(#,##0.00)", + 0x28 => '%1.2f', //"#,##0.00;(#,##0.00)", + 0x29 => '%1.0f', //"#,##0;(#,##0)", + 0x2a => '$%1.0f', //"$#,##0;($#,##0)", + 0x2b => '%1.2f', //"#,##0.00;(#,##0.00)", + 0x2c => '$%1.2f', //"$#,##0.00;($#,##0.00)", + 0x30 => '%1.0f'); //"##0.0E0"; + // }}} + */ + protected $errno = 0; + protected $error = false; + /** + * @var false|SimpleXMLElement + */ + public $theme; + + + public function __construct($filename = null, $is_data = null, $debug = null) + { + if ($debug !== null) { + $this->debug = $debug; + } + $this->package = [ + 'filename' => '', + 'mtime' => 0, + 'size' => 0, + 'comment' => '', + 'entries' => [] + ]; + if ($filename && $this->_unzip($filename, $is_data)) { + $this->_parse(); + } + } + + protected function _unzip($filename, $is_data = false) + { + + if ($is_data) { + $this->package['filename'] = 'default.xlsx'; + $this->package['mtime'] = time(); + $this->package['size'] = $this->_strlen($filename); + + $vZ = $filename; + } else { + if (!is_readable($filename)) { + $this->error(1, 'File not found ' . $filename); + + return false; + } + + // Package information + $this->package['filename'] = $filename; + $this->package['mtime'] = filemtime($filename); + $this->package['size'] = filesize($filename); + + // Read file + $vZ = file_get_contents($filename); + } + // Cut end of central directory + /* $aE = explode("\x50\x4b\x05\x06", $vZ); + + if (count($aE) == 1) { + $this->error('Unknown format'); + return false; + } + */ + // Explode to each part + $aE = explode("\x50\x4b\x03\x04", $vZ); + array_shift($aE); + + $aEL = count($aE); + if ($aEL === 0) { + $this->error(2, 'Unknown archive format'); + + return false; + } + // Search central directory end record + $last = $aE[$aEL - 1]; + $last = explode("\x50\x4b\x05\x06", $last); + if (count($last) !== 2) { + $this->error(2, 'Unknown archive format'); + + return false; + } + // Search central directory + $last = explode("\x50\x4b\x01\x02", $last[0]); + if (count($last) < 2) { + $this->error(2, 'Unknown archive format'); + + return false; + } + $aE[$aEL - 1] = $last[0]; + + // Loop through the entries + foreach ($aE as $vZ) { + $aI = []; + $aI['E'] = 0; + $aI['EM'] = ''; + // Retrieving local file header information +// $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ); + $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL/v1EFL', $vZ); + + // Check if data is encrypted +// $bE = ($aP['GPF'] && 0x0001) ? TRUE : FALSE; +// $bE = false; + $nF = $aP['FNL']; + $mF = $aP['EFL']; + + // Special case : value block after the compressed data + if ($aP['GPF'] & 0x0008) { + $aP1 = unpack('V1CRC/V1CS/V1UCS', $this->_substr($vZ, -12)); + + $aP['CRC'] = $aP1['CRC']; + $aP['CS'] = $aP1['CS']; + $aP['UCS'] = $aP1['UCS']; + // 2013-08-10 + $vZ = $this->_substr($vZ, 0, -12); + if ($this->_substr($vZ, -4) === "\x50\x4b\x07\x08") { + $vZ = $this->_substr($vZ, 0, -4); + } + } + + // Getting stored filename + $aI['N'] = $this->_substr($vZ, 26, $nF); + $aI['N'] = str_replace('\\', '/', $aI['N']); + + if ($this->_substr($aI['N'], -1) === '/') { + // is a directory entry - will be skipped + continue; + } + + // Truncate full filename in path and filename + $aI['P'] = dirname($aI['N']); + $aI['P'] = ($aI['P'] === '.') ? '' : $aI['P']; + $aI['N'] = basename($aI['N']); + + $vZ = $this->_substr($vZ, 26 + $nF + $mF); + + if ($this->_strlen($vZ) !== (int)$aP['CS']) { // check only if availabled + $aI['E'] = 1; + $aI['EM'] = 'Compressed size is not equal with the value in header information.'; + } +// } elseif ( $bE ) { +// $aI['E'] = 5; +// $aI['EM'] = 'File is encrypted, which is not supported from this class.'; + /* } else { + switch ($aP['CM']) { + case 0: // Stored + // Here is nothing to do, the file ist flat. + break; + case 8: // Deflated + $vZ = gzinflate($vZ); + break; + case 12: // BZIP2 + if (extension_loaded('bz2')) { + $vZ = bzdecompress($vZ); + } else { + $aI['E'] = 7; + $aI['EM'] = 'PHP BZIP2 extension not available.'; + } + break; + default: + $aI['E'] = 6; + $aI['EM'] = "De-/Compression method {$aP['CM']} is not supported."; + } + if (!$aI['E']) { + if ($vZ === false) { + $aI['E'] = 2; + $aI['EM'] = 'Decompression of data failed.'; + } elseif ($this->_strlen($vZ) !== (int)$aP['UCS']) { + $aI['E'] = 3; + $aI['EM'] = 'Uncompressed size is not equal with the value in header information.'; + } elseif (crc32($vZ) !== $aP['CRC']) { + $aI['E'] = 4; + $aI['EM'] = 'CRC32 checksum is not equal with the value in header information.'; + } + } + } + */ + + // DOS to UNIX timestamp + $aI['T'] = mktime( + ($aP['FT'] & 0xf800) >> 11, + ($aP['FT'] & 0x07e0) >> 5, + ($aP['FT'] & 0x001f) << 1, + ($aP['FD'] & 0x01e0) >> 5, + $aP['FD'] & 0x001f, + (($aP['FD'] & 0xfe00) >> 9) + 1980 + ); + + $this->package['entries'][] = [ + 'data' => $vZ, + 'ucs' => (int)$aP['UCS'], // ucompresses size + 'cm' => $aP['CM'], // compressed method + 'cs' => isset($aP['CS']) ? (int) $aP['CS'] : 0, // compresses size + 'crc' => $aP['CRC'], + 'error' => $aI['E'], + 'error_msg' => $aI['EM'], + 'name' => $aI['N'], + 'path' => $aI['P'], + 'time' => $aI['T'] + ]; + } // end for each entries + + return true; + } + + protected function _strlen($str) + { + return (ini_get('mbstring.func_overload') & 2) ? mb_strlen($str, '8bit') : strlen($str); + } + + public function error($num = null, $str = null) + { + if ($num) { + $this->errno = $num; + $this->error = $str; + if ($this->debug) { + trigger_error(__CLASS__ . ': ' . $this->error, E_USER_WARNING); + } + } + + return $this->error; + } + + protected function _substr($str, $start, $length = null) + { + return (ini_get('mbstring.func_overload') & 2) ? mb_substr($str, $start, ($length === null) ? mb_strlen($str, '8bit') : $length, '8bit') : substr($str, $start, ($length === null) ? strlen($str) : $length); + } + + protected function _parse() + { + // Document data holders + $this->sharedstrings = []; + $this->sheets = []; +// $this->styles = array(); +// $m1 = 0; // memory_get_peak_usage( true ); + // Read relations and search for officeDocument + if ($relations = $this->getEntryXML('_rels/.rels')) { + foreach ($relations->Relationship as $rel) { + $rel_type = basename(trim((string)$rel['Type'])); // officeDocument + $rel_target = $this->_getTarget('', (string)$rel['Target']); // /xl/workbook.xml or xl/workbook.xml + + if ($rel_type === 'officeDocument' && $workbook = $this->getEntryXML($rel_target)) { + $index_rId = []; // [0 => rId1] + + $index = 0; + foreach ($workbook->sheets->sheet as $s) { + $this->sheetNames[$index] = (string)$s['name']; + $index_rId[$index] = (string)$s['id']; + $index++; + } + if ((int)$workbook->workbookPr['date1904'] === 1) { + $this->date1904 = 1; + } + + + if ($workbookRelations = $this->getEntryXML(dirname($rel_target) . '/_rels/workbook.xml.rels')) { + // Loop relations for workbook and extract sheets... + foreach ($workbookRelations->Relationship as $workbookRelation) { + $wrel_type = basename(trim((string)$workbookRelation['Type'])); // worksheet + $wrel_path = $this->_getTarget(dirname($rel_target), (string)$workbookRelation['Target']); + if (!$this->entryExists($wrel_path)) { + continue; + } + + + if ($wrel_type === 'worksheet') { // Sheets + if ($sheet = $this->getEntryXML($wrel_path)) { + $index = array_search((string)$workbookRelation['Id'], $index_rId, true); + $this->sheets[$index] = $sheet; + $this->sheetFiles[$index] = $wrel_path; + } + } elseif ($wrel_type === 'sharedStrings') { + if ($sharedStrings = $this->getEntryXML($wrel_path)) { + foreach ($sharedStrings->si as $val) { + if (isset($val->t)) { + $this->sharedstrings[] = (string)$val->t; + } elseif (isset($val->r)) { + $this->sharedstrings[] = $this->_parseRichText($val); + } + } + } + } elseif ($wrel_type === 'styles') { + $this->styles = $this->getEntryXML($wrel_path); + + // number formats + $this->nf = []; + if (isset($this->styles->numFmts->numFmt)) { + foreach ($this->styles->numFmts->numFmt as $v) { + $this->nf[(int)$v['numFmtId']] = (string)$v['formatCode']; + } + } + + $this->cellFormats = []; + if (isset($this->styles->cellXfs->xf)) { + foreach ($this->styles->cellXfs->xf as $v) { + $x = [ + 'format' => null + ]; + foreach ($v->attributes() as $k1 => $v1) { + $x[ $k1 ] = (int) $v1; + } + if (isset($x['numFmtId'])) { + if (isset($this->nf[$x['numFmtId']])) { + $x['format'] = $this->nf[$x['numFmtId']]; + } elseif (isset(self::$CF[$x['numFmtId']])) { + $x['format'] = self::$CF[$x['numFmtId']]; + } + } + + $this->cellFormats[] = $x; + } + } + } elseif ($wrel_type === 'theme') { + $this->theme = $this->getEntryXML($wrel_path); + } + } + + break; + } + // reptile hack :: find active sheet from workbook.xml + foreach ($workbook->bookViews->workbookView as $s) { + if (!empty($s['activeTab'])) { + $this->activeSheet = (int)$s['activeTab']; + } + } + } + } + } + +// $m2 = memory_get_peak_usage(true); +// echo __FUNCTION__.' M='.round( ($m2-$m1) / 1048576, 2).'MB'.PHP_EOL; + + if (count($this->sheets)) { + // Sort sheets + ksort($this->sheets); + + return true; + } + + return false; + } + + public function getEntryXML($name) + { + if ($entry_xml = $this->getEntryData($name)) { + $this->deleteEntry($name); // economy memory + // dirty remove namespace prefixes and empty rows + $entry_xml = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $entry_xml); // remove namespaces + $entry_xml .= ' '; // force run garbage collector + $entry_xml = preg_replace('/[a-zA-Z0-9]+:([a-zA-Z0-9]+="[^"]+")/', '$1', $entry_xml); // remove namespaced attrs + $entry_xml .= ' '; + $entry_xml = preg_replace('/<[a-zA-Z0-9]+:([^>]+)>/', '<$1>', $entry_xml); // fix namespaced openned tags + $entry_xml .= ' '; + $entry_xml = preg_replace('/<\/[a-zA-Z0-9]+:([^>]+)>/', '', $entry_xml); // fix namespaced closed tags + $entry_xml .= ' '; + + if (strpos($name, '/sheet')) { // dirty skip empty rows + // remove + $entry_xml = preg_replace('/]+>\s*(\s*)+<\/row>/', '', $entry_xml, -1, $cnt); + $entry_xml .= ' '; + // remove + $entry_xml = preg_replace('/]*\/>/', '', $entry_xml, -1, $cnt2); + $entry_xml .= ' '; + // remove + $entry_xml = preg_replace('/]*><\/row>/', '', $entry_xml, -1, $cnt3); + $entry_xml .= ' '; + if ($cnt || $cnt2 || $cnt3) { + $entry_xml = preg_replace('//', '', $entry_xml); + $entry_xml .= ' '; + } +// file_put_contents( basename( $name ), $entry_xml ); // @to do comment!!! + } + $entry_xml = trim($entry_xml); + +// $m1 = memory_get_usage(); + // XML External Entity (XXE) Prevention, libxml_disable_entity_loader deprecated in PHP 8 + if (LIBXML_VERSION < 20900 && function_exists('libxml_disable_entity_loader')) { + $_old = libxml_disable_entity_loader(); + } + + $_old_uie = libxml_use_internal_errors(true); + + $entry_xmlobj = simplexml_load_string($entry_xml, 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_PARSEHUGE); + + libxml_use_internal_errors($_old_uie); + + if (LIBXML_VERSION < 20900 && function_exists('libxml_disable_entity_loader')) { + /** @noinspection PhpUndefinedVariableInspection */ + libxml_disable_entity_loader($_old); + } + +// $m2 = memory_get_usage(); +// echo round( ($m2-$m1) / (1024 * 1024), 2).' MB'.PHP_EOL; + + if ($entry_xmlobj) { + return $entry_xmlobj; + } + $e = libxml_get_last_error(); + if ($e) { + $this->error(3, 'XML-entry ' . $name . ' parser error ' . $e->message . ' line ' . $e->line); + } + } else { + $this->error(4, 'XML-entry not found ' . $name); + } + + return false; + } + + // sheets numeration: 1,2,3.... + + public function getEntryData($name) + { + $name = ltrim(str_replace('\\', '/', $name), '/'); + $dir = $this->_strtoupper(dirname($name)); + $name = $this->_strtoupper(basename($name)); + foreach ($this->package['entries'] as &$entry) { + if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) { + if ($entry['error']) { + return false; + } + switch ($entry['cm']) { + case -1: + case 0: // Stored + // Here is nothing to do, the file ist flat. + break; + case 8: // Deflated + $entry['data'] = gzinflate($entry['data']); + break; + case 12: // BZIP2 + if (extension_loaded('bz2')) { + $entry['data'] = bzdecompress($entry['data']); + } else { + $entry['error'] = 7; + $entry['error_message'] = 'PHP BZIP2 extension not available.'; + } + break; + default: + $entry['error'] = 6; + $entry['error_msg'] = 'De-/Compression method '.$entry['cm'].' is not supported.'; + } + if (!$entry['error'] && $entry['cm'] > -1) { + $entry['cm'] = -1; + if ($entry['data'] === false) { + $entry['error'] = 2; + $entry['error_msg'] = 'Decompression of data failed.'; + } elseif ($this->_strlen($entry['data']) !== (int)$entry['ucs']) { + $entry['error'] = 3; + $entry['error_msg'] = 'Uncompressed size is not equal with the value in header information.'; + } elseif (crc32($entry['data']) !== $entry['crc']) { + $entry['error'] = 4; + $entry['error_msg'] = 'CRC32 checksum is not equal with the value in header information.'; + } + } + + return $entry['data']; + } + } + unset($entry); + $this->error(5, 'Entry not found ' . ($dir ? $dir . '/' : '') . $name); + + return false; + } + public function deleteEntry($name) + { + $name = ltrim(str_replace('\\', '/', $name), '/'); + $dir = $this->_strtoupper(dirname($name)); + $name = $this->_strtoupper(basename($name)); + foreach ($this->package['entries'] as $k => $entry) { + if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) { + unset($this->package['entries'][$k]); + return true; + } + } + return false; + } + + protected function _strtoupper($str) + { + return (ini_get('mbstring.func_overload') & 2) ? mb_strtoupper($str, '8bit') : strtoupper($str); + } + + protected function _getTarget($base, $target) + { + $target = trim($target); + if (strpos($target, '/') === 0) { + return $this->_substr($target, 1); + } + $target = ($base ? $base . '/' : '') . $target; + // a/b/../c -> a/c + $parts = explode('/', $target); + $abs = []; + foreach ($parts as $p) { + if ('.' === $p) { + continue; + } + if ('..' === $p) { + array_pop($abs); + } else { + $abs[] = $p; + } + } + return implode('/', $abs); + } + + /* + * @param string $name Filename in archive + * @return SimpleXMLElement|bool + */ + + public function entryExists($name) + { + // 0.6.6 + $dir = $this->_strtoupper(dirname($name)); + $name = $this->_strtoupper(basename($name)); + foreach ($this->package['entries'] as $entry) { + if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) { + return true; + } + } + + return false; + } + + protected function _parseRichText($is = null) + { + $value = []; + + if (isset($is->t)) { + $value[] = (string)$is->t; + } elseif (isset($is->r)) { + foreach ($is->r as $run) { + $value[] = (string)$run->t; + } + } + + return implode('', $value); + } + + public static function parseFile($filename, $debug = false) + { + return self::parse($filename, false, $debug); + } + + public static function parse($filename, $is_data = false, $debug = false) + { + $xlsx = new self(); + $xlsx->debug = $debug; + if ($xlsx->_unzip($filename, $is_data)) { + $xlsx->_parse(); + } + if ($xlsx->success()) { + return $xlsx; + } + self::parseError($xlsx->error()); + self::parseErrno($xlsx->errno()); + + return false; + } + + public function success() + { + return !$this->error; + } + + // https://github.com/shuchkin/simplexlsx#gets-extend-cell-info-by--rowsex + + public static function parseError($set = false) + { + static $error = false; + + return $set ? $error = $set : $error; + } + + public static function parseErrno($set = false) + { + static $errno = false; + + return $set ? $errno = $set : $errno; + } + + public function errno() + { + return $this->errno; + } + + public static function parseData($data, $debug = false) + { + return self::parse($data, true, $debug); + } + + + + public function worksheet($worksheetIndex = 0) + { + + + if (isset($this->sheets[$worksheetIndex])) { + $ws = $this->sheets[$worksheetIndex]; + + if (!isset($this->hyperlinks[$worksheetIndex]) && isset($ws->hyperlinks)) { + $this->hyperlinks[$worksheetIndex] = []; + $sheet_rels = str_replace('worksheets', 'worksheets/_rels', $this->sheetFiles[$worksheetIndex]) . '.rels'; + $link_ids = []; + + if ($rels = $this->getEntryXML($sheet_rels)) { + // hyperlink +// $rel_base = dirname( $sheet_rels ); + foreach ($rels->Relationship as $rel) { + $rel_type = basename(trim((string)$rel['Type'])); + if ($rel_type === 'hyperlink') { + $rel_id = (string)$rel['Id']; + $rel_target = (string)$rel['Target']; + $link_ids[$rel_id] = $rel_target; + } + } + } + foreach ($ws->hyperlinks->hyperlink as $hyperlink) { + $ref = (string)$hyperlink['ref']; + if ($this->_strpos($ref, ':') > 0) { // A1:A8 -> A1 + $ref = explode(':', $ref); + $ref = $ref[0]; + } +// $this->hyperlinks[ $worksheetIndex ][ $ref ] = (string) $hyperlink['display']; + $loc = (string)$hyperlink['location']; + $id = (string)$hyperlink['id']; + if ($id) { + $href = $link_ids[$id] . ($loc ? '#' . $loc : ''); + } else { + $href = $loc; + } + $this->hyperlinks[$worksheetIndex][$ref] = $href; + } + } + + return $ws; + } + $this->error(6, 'Worksheet not found ' . $worksheetIndex); + + return false; + } + + protected function _strpos($haystack, $needle, $offset = 0) + { + return (ini_get('mbstring.func_overload') & 2) ? mb_strpos($haystack, $needle, $offset, '8bit') : strpos($haystack, $needle, $offset); + } + + /** + * returns [numCols,numRows] of worksheet + * + * @param int $worksheetIndex + * + * @return array + */ + public function dimension($worksheetIndex = 0) + { + + if (($ws = $this->worksheet($worksheetIndex)) === false) { + return [0, 0]; + } + /* @var SimpleXMLElement $ws */ + + $ref = (string)$ws->dimension['ref']; + + if ($this->_strpos($ref, ':') !== false) { + $d = explode(':', $ref); + $idx = $this->getIndex($d[1]); + + return [$idx[0] + 1, $idx[1] + 1]; + } + /* + if ( $ref !== '' ) { // 0.6.8 + $index = $this->getIndex( $ref ); + + return [ $index[0] + 1, $index[1] + 1 ]; + } + */ + + // slow method + $maxC = $maxR = 0; + foreach ($ws->sheetData->row as $row) { + foreach ($row->c as $c) { + $idx = $this->getIndex((string)$c['r']); + $x = $idx[0]; + $y = $idx[1]; + if ($x > 0) { + if ($x > $maxC) { + $maxC = $x; + } + if ($y > $maxR) { + $maxR = $y; + } + } + } + } + + return [$maxC + 1, $maxR + 1]; + } + + public function getIndex($cell = 'A1') + { + + if (preg_match('/([A-Z]+)(\d+)/', $cell, $m)) { + $col = $m[1]; + $row = $m[2]; + + $colLen = $this->_strlen($col); + $index = 0; + + for ($i = $colLen - 1; $i >= 0; $i--) { + $index += (ord($col[$i]) - 64) * pow(26, $colLen - $i - 1); + } + + return [$index - 1, $row - 1]; + } + +// $this->error( 'Invalid cell index ' . $cell ); + + return [-1, -1]; + } + + public function value($cell) + { + // Determine data type + $dataType = (string)$cell['t']; + + if ($dataType === '' || $dataType === 'n') { // number + $s = (int)$cell['s']; + if ($s > 0 && isset($this->cellFormats[$s])) { + if (array_key_exists('format', $this->cellFormats[$s])) { + $format = $this->cellFormats[$s]['format']; + if (preg_match('/(m|AM|PM)/', preg_replace('/\"[^"]+\"/', '', $format))) { // [mm]onth,AM|PM + $dataType = 'D'; + } + } else { + $dataType = 'n'; + } + } + } + + $value = ''; + + switch ($dataType) { + case 's': + // Value is a shared string + if ((string)$cell->v !== '') { + $value = $this->sharedstrings[(int)$cell->v]; + } + break; + + case 'str': // formula? + if ((string)$cell->v !== '') { + $value = (string)$cell->v; + } + break; + + case 'b': + // Value is boolean + $value = (string)$cell->v; + if ($value === '0') { + $value = false; + } elseif ($value === '1') { + $value = true; + } else { + $value = (bool)$cell->v; + } + + break; + + case 'inlineStr': + // Value is rich text inline + $value = $this->_parseRichText($cell->is); + + break; + + case 'e': + // Value is an error message + if ((string)$cell->v !== '') { + $value = (string)$cell->v; + } + break; + + case 'D': + // Date as float + if (!empty($cell->v)) { + $value = $this->datetimeFormat ? gmdate($this->datetimeFormat, $this->unixstamp((float)$cell->v)) : (float)$cell->v; + } + break; + + case 'd': + // Date as ISO YYYY-MM-DD + if ((string)$cell->v !== '') { + $value = (string)$cell->v; + } + break; + + default: + // Value is a string + $value = (string)$cell->v; + + // Check for numeric values + if (is_numeric($value)) { + /** @noinspection TypeUnsafeComparisonInspection */ + if ($value == (int)$value) { + $value = (int)$value; + } /** @noinspection TypeUnsafeComparisonInspection */ elseif ($value == (float)$value) { + $value = (float)$value; + } + } + } + + return $value; + } + + public function unixstamp($excelDateTime) + { + + $d = floor($excelDateTime); // days since 1900 or 1904 + $t = $excelDateTime - $d; + + if ($this->date1904) { + $d += 1462; + } + + $t = (abs($d) > 0) ? ($d - 25569) * 86400 + round($t * 86400) : round($t * 86400); + + return (int)$t; + } + + public function href($worksheetIndex, $cell) + { + $ref = (string)$cell['r']; + return isset($this->hyperlinks[$worksheetIndex][$ref]) ? $this->hyperlinks[$worksheetIndex][$ref] : ''; + } + + public function toHTML($worksheetIndex = 0) + { + $s = ''; + foreach ($this->readRows($worksheetIndex) as $r) { + $s .= ''; + foreach ($r as $c) { + $s .= ''; + } + $s .= "\r\n"; + } + $s .= '
' . ($c === '' ? ' ' : htmlspecialchars($c, ENT_QUOTES)) . '
'; + + return $s; + } + public function toHTMLEx($worksheetIndex = 0) + { + $s = ''; + $y = 0; + foreach ($this->readRowsEx($worksheetIndex) as $r) { + $s .= ''; + $x = 0; + foreach ($r as $c) { + $tag = 'td'; + $css = $c['css']; + if ($y === 0) { + $tag = 'th'; + $css .= $c['width'] ? 'width: '.round($c['width'] * 0.47, 2).'em;' : ''; + } + + if ($x === 0 && $c['height']) { + $css .= 'height: '.round($c['height'] * 1.3333).'px;'; + } + $s .= '<'.$tag.' style="'.$css.'" nowrap>' . ($c['value'] === '' ? ' ' : htmlspecialchars($c['value'], ENT_QUOTES)) . ''; + $x++; + } + $s .= "\r\n"; + $y++; + } + $s .= '
'; + + return $s; + } + public function rows($worksheetIndex = 0, $limit = 0) + { + return iterator_to_array($this->readRows($worksheetIndex, $limit), false); + } + // thx Gonzo + /** + * @param $worksheetIndex + * @param $limit + * @return \Generator + */ + public function readRows($worksheetIndex = 0, $limit = 0) + { + + if (($ws = $this->worksheet($worksheetIndex)) === false) { + return; + } + $dim = $this->dimension($worksheetIndex); + $numCols = $dim[0]; + $numRows = $dim[1]; + + $emptyRow = []; + for ($i = 0; $i < $numCols; $i++) { + $emptyRow[] = ''; + } + + $curR = 0; + $_limit = $limit; + /* @var SimpleXMLElement $ws */ + foreach ($ws->sheetData->row as $row) { + $r = $emptyRow; + $curC = 0; + foreach ($row->c as $c) { + // detect skipped cols + $idx = $this->getIndex((string)$c['r']); + $x = $idx[0]; + $y = $idx[1]; + + if ($x > -1) { + $curC = $x; + while ($curR < $y) { + yield $emptyRow; + $curR++; + $_limit--; + if ($_limit === 0) { + return; + } + } + } + $r[$curC] = $this->value($c); + $curC++; + } + yield $r; + + $curR++; + $_limit--; + if ($_limit === 0) { + return; + } + } + while ($curR < $numRows) { + yield $emptyRow; + $curR++; + $_limit--; + if ($_limit === 0) { + return; + } + } + } + + public function rowsEx($worksheetIndex = 0, $limit = 0) + { + return iterator_to_array($this->readRowsEx($worksheetIndex, $limit), false); + } + // https://github.com/shuchkin/simplexlsx#gets-extend-cell-info-by--rowsex + /** + * @param $worksheetIndex + * @param $limit + * @return \Generator|null + */ + public function readRowsEx($worksheetIndex = 0, $limit = 0) + { + if (!$this->rowsExReader) { + require_once __DIR__ . '/SimpleXLSXEx.php'; + $this->rowsExReader = new SimpleXLSXEx($this); + } + return $this->rowsExReader->readRowsEx($worksheetIndex, $limit); + } + + /** + * Returns cell value + * VERY SLOW! Use ->rows() or ->rowsEx() + * + * @param int $worksheetIndex + * @param string|array $cell ref or coords, D12 or [3,12] + * + * @return mixed Returns NULL if not found + */ + public function getCell($worksheetIndex = 0, $cell = 'A1') + { + + if (($ws = $this->worksheet($worksheetIndex)) === false) { + return false; + } + if (is_array($cell)) { + $cell = $this->_num2name($cell[0]) . $cell[1];// [3,21] -> D21 + } + if (is_string($cell)) { + $result = $ws->sheetData->xpath("row/c[@r='" . $cell . "']"); + if (count($result)) { + return $this->value($result[0]); + } + } + + return null; + } + + protected function _num2name($num) + { + $numeric = ($num - 1) % 26; + $letter = chr(65 + $numeric); + $num2 = (int)(($num - 1) / 26); + if ($num2 > 0) { + return $this->_num2name($num2) . $letter; + } + return $letter; + } + + public function getSheets() + { + return $this->sheets; + } + + public function sheetsCount() + { + return count($this->sheets); + } + + public function sheetName($worksheetIndex) + { + if (isset($this->sheetNames[$worksheetIndex])) { + return $this->sheetNames[$worksheetIndex]; + } + + return false; + } + + public function sheetNames() + { + + return $this->sheetNames; + } + + public function getStyles() + { + return $this->styles; + } + + public function getPackage() + { + return $this->package; + } + + public function setDateTimeFormat($value) + { + $this->datetimeFormat = is_string($value) ? $value : false; + } +} diff --git a/spreadSheetReader/SimpleXLSXGen.php b/spreadSheetReader/SimpleXLSXGen.php new file mode 100644 index 0000000..459c158 --- /dev/null +++ b/spreadSheetReader/SimpleXLSXGen.php @@ -0,0 +1,1015 @@ +curSheet = -1; + $this->defaultFont = 'Calibri'; + $this->sheets = [['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '']]; + $this->extLinkId = 0; + $this->SI = []; // sharedStrings index + $this->SI_KEYS = []; // & keys + + // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_numFmts_topic_ID0E6KK6.html + $this->NF = [ + self::N_RUB => '#,##0.00\ "₽"', + self::N_DOLLAR => '[$$-1]#,##0.00', + self::N_EURO => '#,##0.00\ [$€-1]' + ]; + $this->NF_KEYS = array_flip($this->NF); + + $this->BR_STYLE = [ + self::B_NONE => 'none', + self::B_THIN => 'thin', + self::B_MEDIUM => 'medium', + self::B_DASHED => 'dashed', + self::B_DOTTED => 'dotted', + self::B_THICK => 'thick', + self::B_DOUBLE => 'double', + self::B_HAIR => 'hair', + self::B_MEDIUM_DASHED => 'mediumDashed', + self::B_DASH_DOT => 'dashDot', + self::B_MEDIUM_DASH_DOT => 'mediumDashDot', + self::B_DASH_DOT_DOT => 'dashDotDot', + self::B_MEDIUM_DASH_DOT_DOT => 'mediumDashDotDot', + self::B_SLANT_DASH_DOT => 'slantDashDot' + ]; + + + $this->XF = [ // styles 0 - num fmt, 1 - align, 2 - font, 3 - fill, 4 - font color, 5 - bgcolor, 6 - border + [self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_NONE, 0, 0, ''], + [self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_GRAY_125, 0, 0, ''], // hack + ]; + $this->XF_KEYS[implode('-', $this->XF[0])] = 0; // & keys + $this->XF_KEYS[implode('-', $this->XF[1])] = 1; + + + $this->template = [ + '_rels/.rels' => ' + + + + +', + 'docProps/app.xml' => ' + +0 +' . __CLASS__ . '', + 'docProps/core.xml' => ' + +{DATE} +en-US +{DATE} +1 +', + 'xl/_rels/workbook.xml.rels' => ' + + +{SHEETS}', + 'xl/worksheets/sheet1.xml' => ' +{COLS}{ROWS}{AUTOFILTER}{MERGECELLS}{HYPERLINKS}', + 'xl/worksheets/_rels/sheet1.xml.rels' => ' +{HYPERLINKS}', + 'xl/sharedStrings.xml' => ' +{STRINGS}', + 'xl/styles.xml' => ' + +{NUMFMTS} +{FONTS} +{FILLS} +{BORDERS} + +{XF} + + + +', + 'xl/workbook.xml' => ' + + +{SHEETS} +', + '[Content_Types].xml' => ' + + + + + + + + +{TYPES} +', + ]; + + // + // 01001200 + // Простой шаблонБудем делать генератор + } + + public static function fromArray(array $rows, $sheetName = null) + { + return (new static())->addSheet($rows, $sheetName); + } + + public function addSheet(array $rows, $name = null) + { + + $this->curSheet++; + if ($name === null) { // autogenerated sheet names + $name = 'Sheet' . ($this->curSheet + 1); + } else { + $name = mb_substr($name, 0, 31); + $names = []; + foreach ($this->sheets as $sh) { + $names[mb_strtoupper($sh['name'])] = 1; + } + for ($i = 0; $i < 100; $i++) { + $postfix = ' (' . $i . ')'; + $new_name = ($i === 0) ? $name : $name . $postfix; + if (mb_strlen($new_name) > 31) { + $new_name = mb_substr($name, 0, 31 - mb_strlen($postfix)) . $postfix; + } + $NEW_NAME = mb_strtoupper($new_name); + if (!isset($names[$NEW_NAME])) { + $name = $new_name; + break; + } + } + } + + $this->sheets[$this->curSheet] = ['name' => $name, 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '']; + + if (isset($rows[0]) && is_array($rows[0])) { + $this->sheets[$this->curSheet]['rows'] = $rows; + } else { + $this->sheets[$this->curSheet]['rows'] = []; + } + return $this; + } + + public function __toString() + { + $fh = fopen('php://memory', 'wb'); + if (!$fh) { + return ''; + } + + if (!$this->_write($fh)) { + fclose($fh); + return ''; + } + $size = ftell($fh); + fseek($fh, 0); + + return (string)fread($fh, $size); + } + + public function saveAs($filename) + { + $fh = fopen($filename, 'wb'); + if (!$fh) { + return false; + } + if (!$this->_write($fh)) { + fclose($fh); + return false; + } + fclose($fh); + + return true; + } + + public function download() + { + return $this->downloadAs(gmdate('YmdHi') . '.xlsx'); + } + + public function downloadAs($filename) + { + $fh = fopen('php://memory', 'wb'); + if (!$fh) { + return false; + } + + if (!$this->_write($fh)) { + fclose($fh); + return false; + } + + $size = ftell($fh); + + header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', time())); + header('Content-Length: ' . $size); + + while (ob_get_level()) { + ob_end_clean(); + } + fseek($fh, 0); + fpassthru($fh); + + fclose($fh); + return true; + } + + protected function _write($fh) + { + + + $dirSignatureE = "\x50\x4b\x05\x06"; // end of central dir signature + $zipComments = 'Generated by ' . __CLASS__ . ' PHP class, thanks sergey.shuchkin@gmail.com'; + + if (!$fh) { + return false; + } + + $cdrec = ''; // central directory content + $entries = 0; // number of zipped files + $cnt_sheets = count($this->sheets); + + foreach ($this->template as $cfilename => $template) { + if ($cfilename === 'xl/_rels/workbook.xml.rels') { + $s = ''; + for ($i = 0; $i < $cnt_sheets; $i++) { + $s .= '\n"; + } + $s .= ''; + $template = str_replace('{SHEETS}', $s, $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ($cfilename === 'xl/workbook.xml') { + $s = ''; + foreach ($this->sheets as $k => $v) { + $s .= ''; + } + $template = str_replace('{SHEETS}', $s, $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ($cfilename === 'docProps/core.xml') { + $template = str_replace('{DATE}', gmdate('Y-m-d\TH:i:s\Z'), $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ($cfilename === 'xl/sharedStrings.xml') { + if (!count($this->SI)) { + $this->SI[] = 'No Data'; + } + $si_cnt = count($this->SI); + $si = '' . implode("\r\n", $this->SI) . ''; + $template = str_replace(['{CNT}', '{STRINGS}'], [$si_cnt, $si], $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ($cfilename === 'xl/worksheets/sheet1.xml') { + foreach ($this->sheets as $k => $v) { + $filename = 'xl/worksheets/sheet' . ($k + 1) . '.xml'; + $xml = $this->_sheetToXML($k, $template); + $this->_writeEntry($fh, $cdrec, $filename, $xml); + $entries++; + } + $xml = null; + } elseif ($cfilename === 'xl/worksheets/_rels/sheet1.xml.rels') { + foreach ($this->sheets as $k => $v) { + if ($this->extLinkId) { + $RH = []; + $filename = 'xl/worksheets/_rels/sheet' . ($k + 1) . '.xml.rels'; + foreach ($v['hyperlinks'] as $h) { + if ($h['ID']) { + $RH[] = ''; + } + } + $xml = str_replace('{HYPERLINKS}', implode("\r\n", $RH), $template); + $this->_writeEntry($fh, $cdrec, $filename, $xml); + $entries++; + } + } + $xml = null; + + } elseif ($cfilename === '[Content_Types].xml') { + $TYPES = ['']; + foreach ($this->sheets as $k => $v) { + $TYPES[] = ''; + if ($this->extLinkId) { + $TYPES[] = ''; + } + } + $template = str_replace('{TYPES}', implode("\r\n", $TYPES), $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ($cfilename === 'xl/styles.xml') { + $NF = $XF = $FONTS = $F_KEYS = $FILLS = $FL_KEYS = []; + $BR = ['']; + $BR_KEYS = [0 => 0]; + + foreach ($this->NF as $k => $v) { + $NF[] = ''; + } + + foreach ($this->XF as $xf) { + // 0 - num fmt, 1 - align, 2 - font, 3 - fill, 4 - font color, 5 - bgcolor, 6 - border + // fonts + $F_KEY = $xf[2] . '-' . $xf[4]; + if (isset($F_KEYS[$F_KEY])) { + $F_ID = $F_KEYS[$F_KEY]; + } else { + $F_ID = $F_KEYS[$F_KEY] = count($FONTS); + + $FONTS[] = '' + . ($this->defaultFontSize ? '' : '') + . ($xf[2] & self::F_BOLD ? '' : '') + . ($xf[2] & self::F_ITALIC ? '' : '') + . ($xf[2] & self::F_UNDERLINE ? '' : '') + . ($xf[2] & self::F_STRIKE ? '' : '') + . ($xf[2] & self::F_HYPERLINK ? '' : '') + . ($xf[2] & self::F_COLOR ? '' : '') + . ''; + } + // fills + $FL_KEY = $xf[3] . '-' . $xf[5]; + if (isset($FL_KEYS[$FL_KEY])) { + $FL_ID = $FL_KEYS[$FL_KEY]; + } else { + $FL_ID = $FL_KEYS[$FL_KEY] = count($FILLS); + $FILLS[] = '' : ' />') + . ''; + } + $align = ($xf[1] & self::A_LEFT ? ' horizontal="left"' : '') + . ($xf[1] & self::A_RIGHT ? ' horizontal="right"' : '') + . ($xf[1] & self::A_CENTER ? ' horizontal="center"' : '') + . ($xf[1] & self::A_TOP ? ' vertical="top"' : '') + . ($xf[1] & self::A_MIDDLE ? ' vertical="center"' : '') + . ($xf[1] & self::A_BOTTOM ? ' vertical="bottom"' : '') + . ($xf[1] & self::A_WRAPTEXT ? ' wrapText="1"' : ''); + + // border + $BR_ID = 0; + if ($xf[6] !== '') { + $b = $xf[6]; + if (isset($BR_KEYS[$b])) { + $BR_ID = $BR_KEYS[$b]; + } else { + $BR_ID = count($BR_KEYS); + $BR_KEYS[$b] = $BR_ID; + $border = ''; + $ba = explode(' ', $b); + if (!isset($ba[1])) { + $ba[] = $ba[0]; + $ba[] = $ba[0]; + $ba[] = $ba[0]; + } + if (!isset($ba[4])) { // diagonal + $ba[] = 'none'; + } + $sides = [ 'left' => 3, 'right' => 1, 'top' => 0, 'bottom' => 2, 'diagonal' => 4]; + foreach ($sides as $side => $idx) { + $s = 'thin'; + $c = ''; + + $va = explode('#', $ba[$idx]); + if (isset($va[1])) { + $s = $va[0] === '' ? 'thin' : $va[0]; + $c = $va[1]; + } elseif (in_array($va[0], $this->BR_STYLE, true)) { + $s = $va[0]; + } else { + $c = $va[0]; + } + if (strlen($c) === 6) { + $c = 'FF' . $c; + } + + if ($s && $s !== 'none') { + $border .= '<' . $side . ' style="' . $s . '">' + . '' + . ''; + } else { + $border .= '<' . $side . '/>'; + } + + } + $border .= ''; + $BR[] = $border; + } + } + + + $XF[] = ' 0 ? ' applyNumberFormat="1"' : '') + . ($F_ID > 0 ? ' applyFont="1"' : '') + . ($FL_ID > 0 ? ' applyFill="1"' : '') + . ($BR_ID > 0 ? ' applyBorder="1"' : '') + . ($align ? ' applyAlignment="1">' : '/>'); + + } + + + // wrap collections + array_unshift($NF, ''); + $NF[] = ''; + array_unshift($XF, ''); + $XF[] = ''; + array_unshift($FONTS, ''); + $FONTS[] = ''; + array_unshift($FILLS, ''); + $FILLS[] = ''; + array_unshift($BR, ''); + $BR[] = ''; + + $template = str_replace( + ['{NUMFMTS}', '{FONTS}', '{XF}', '{FILLS}', '{BORDERS}'], + [implode("\r\n", $NF), implode("\r\n", $FONTS), implode("\r\n", $XF), implode("\r\n", $FILLS), implode("\r\n", $BR)], + $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } else { + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } + } + $before_cd = ftell($fh); + fwrite($fh, $cdrec); + + // end of central dir + fwrite($fh, $dirSignatureE); + fwrite($fh, pack('v', 0)); // number of this disk + fwrite($fh, pack('v', 0)); // number of the disk with the start of the central directory + fwrite($fh, pack('v', $entries)); // total # of entries "on this disk" + fwrite($fh, pack('v', $entries)); // total # of entries overall + fwrite($fh, pack('V', mb_strlen($cdrec, '8bit'))); // size of central dir + fwrite($fh, pack('V', $before_cd)); // offset to start of central dir + fwrite($fh, pack('v', mb_strlen($zipComments, '8bit'))); // .zip file comment length + fwrite($fh, $zipComments); + + return true; + } + + protected function _writeEntry($fh, &$cdrec, $cfilename, $data) + { + $zipSignature = "\x50\x4b\x03\x04"; // local file header signature + $dirSignature = "\x50\x4b\x01\x02"; // central dir header signature + + $e = []; + $e['uncsize'] = mb_strlen($data, '8bit'); + + // if data to compress is too small, just store it + if ($e['uncsize'] < 256) { + $e['comsize'] = $e['uncsize']; + $e['vneeded'] = 10; + $e['cmethod'] = 0; + $zdata = $data; + } else { // otherwise, compress it + $zdata = gzcompress($data); + $zdata = substr(substr($zdata, 0, -4), 2); // fix crc bug (thanks to Eric Mueller) + $e['comsize'] = mb_strlen($zdata, '8bit'); + $e['vneeded'] = 10; + $e['cmethod'] = 8; + } + + $e['bitflag'] = 0; + $e['crc_32'] = crc32($data); + + // Convert date and time to DOS Format, and set then + $lastmod_timeS = str_pad(decbin(date('s') >= 32 ? date('s') - 32 : date('s')), 5, '0', STR_PAD_LEFT); + $lastmod_timeM = str_pad(decbin(date('i')), 6, '0', STR_PAD_LEFT); + $lastmod_timeH = str_pad(decbin(date('H')), 5, '0', STR_PAD_LEFT); + $lastmod_dateD = str_pad(decbin(date('d')), 5, '0', STR_PAD_LEFT); + $lastmod_dateM = str_pad(decbin(date('m')), 4, '0', STR_PAD_LEFT); + $lastmod_dateY = str_pad(decbin(date('Y') - 1980), 7, '0', STR_PAD_LEFT); + + # echo "ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (".date("s H H").")\n"; + # echo "ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (".date("d m Y").")\n"; + $e['modtime'] = bindec("$lastmod_timeH$lastmod_timeM$lastmod_timeS"); + $e['moddate'] = bindec("$lastmod_dateY$lastmod_dateM$lastmod_dateD"); + + $e['offset'] = ftell($fh); + + fwrite($fh, $zipSignature); + fwrite($fh, pack('s', $e['vneeded'])); // version_needed + fwrite($fh, pack('s', $e['bitflag'])); // general_bit_flag + fwrite($fh, pack('s', $e['cmethod'])); // compression_method + fwrite($fh, pack('s', $e['modtime'])); // lastmod_time + fwrite($fh, pack('s', $e['moddate'])); // lastmod_date + fwrite($fh, pack('V', $e['crc_32'])); // crc-32 + fwrite($fh, pack('I', $e['comsize'])); // compressed_size + fwrite($fh, pack('I', $e['uncsize'])); // uncompressed_size + fwrite($fh, pack('s', mb_strlen($cfilename, '8bit'))); // file_name_length + fwrite($fh, pack('s', 0)); // extra_field_length + fwrite($fh, $cfilename); // file_name + // ignoring extra_field + fwrite($fh, $zdata); + + // Append it to central dir + $e['external_attributes'] = (substr($cfilename, -1) === '/' && !$zdata) ? 16 : 32; // Directory or file name + $e['comments'] = ''; + + $cdrec .= $dirSignature; + $cdrec .= "\x0\x0"; // version made by + $cdrec .= pack('v', $e['vneeded']); // version needed to extract + $cdrec .= "\x0\x0"; // general bit flag + $cdrec .= pack('v', $e['cmethod']); // compression method + $cdrec .= pack('v', $e['modtime']); // lastmod time + $cdrec .= pack('v', $e['moddate']); // lastmod date + $cdrec .= pack('V', $e['crc_32']); // crc32 + $cdrec .= pack('V', $e['comsize']); // compressed filesize + $cdrec .= pack('V', $e['uncsize']); // uncompressed filesize + $cdrec .= pack('v', mb_strlen($cfilename, '8bit')); // file name length + $cdrec .= pack('v', 0); // extra field length + $cdrec .= pack('v', mb_strlen($e['comments'], '8bit')); // file comment length + $cdrec .= pack('v', 0); // disk number start + $cdrec .= pack('v', 0); // internal file attributes + $cdrec .= pack('V', $e['external_attributes']); // internal file attributes + $cdrec .= pack('V', $e['offset']); // relative offset of local header + $cdrec .= $cfilename; + $cdrec .= $e['comments']; + } + + protected function _sheetToXML($idx, $template) + { + // locale floats fr_FR 1.234,56 -> 1234.56 + $_loc = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, 'C'); + $COLS = []; + $ROWS = []; + if (count($this->sheets[$idx]['rows'])) { + $COLS[] = ''; + $CUR_ROW = 0; + $COL = []; + foreach ($this->sheets[$idx]['rows'] as $r) { + $CUR_ROW++; + $row = ''; + $CUR_COL = 0; + $RH = 0; // row height + foreach ($r as $v) { + $CUR_COL++; + if (!isset($COL[$CUR_COL])) { + $COL[$CUR_COL] = 0; + } + $cname = $this->num2name($CUR_COL) . $CUR_ROW; + + if ($v === null || $v === '') { + $row .= ''; + continue; + } + + $ct = $cv = $cf = null; + $N = $A = $F = $FL = $C = $BG = 0; + $BR = ''; + + if (is_string($v)) { + + if ($v[0] === "\0") { // RAW value as string + $v = substr($v, 1); + $vl = mb_strlen($v); + } else { + if (strpos($v, '<') !== false) { // tags? + if (strpos($v, '') !== false) { + $F += self::F_BOLD; + } + if (strpos($v, '') !== false) { + $F += self::F_ITALIC; + } + if (strpos($v, '') !== false) { + $F += self::F_UNDERLINE; + } + if (strpos($v, '') !== false) { + $F += self::F_STRIKE; + } + if (preg_match('/]+)>/', $v, $m)) { + + if (preg_match('/ color="([^"]+)"/', $m[1], $m2)) { + + $F += self::F_COLOR; + $c = ltrim($m2[1], '#'); + $C = strlen($c) === 8 ? $c : ('FF' . $c); + } + if (preg_match('/ bgcolor="([^"]+)"/', $m[1], $m2)) { + $FL += self::FL_COLOR; + $c = ltrim($m2[1], '#'); + $BG = strlen($c) === 8 ? $c : ('FF' . $c); + } + if (preg_match('/ height="([^"]+)"/', $m[1], $m2)) { + $RH = $m2[1]; + } + if (preg_match('/ nf="([^"]+)"/', $m[1], $m2)) { + $c = htmlspecialchars_decode($m2[1], ENT_QUOTES); + $N = $this->getNumFmtId($c); + } + if (preg_match('/ border="([^"]+)"/', $m[1], $m2)) { + $b = htmlspecialchars_decode($m2[1], ENT_QUOTES); + if ($b && $b !== 'none') { + $BR = $b; + } + } + } + if (strpos($v, '') !== false) { + $A += self::A_LEFT; + } + if (strpos($v, '
') !== false) { + $A += self::A_CENTER; + } + if (strpos($v, '') !== false) { + $A += self::A_RIGHT; + } + if (strpos($v, '') !== false) { + $A += self::A_TOP; + } + if (strpos($v, '') !== false) { + $A += self::A_MIDDLE; + } + if (strpos($v, '') !== false) { + $A += self::A_BOTTOM; + } + if (strpos($v, '') !== false) { + $A += self::A_WRAPTEXT; + } + if (preg_match('/(.*?)<\/a>/i', $v, $m)) { + $h = explode('#', $m[1]); + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => $h[0], 'L' => isset($h[1]) ? $h[1] : '']; + $F += self::F_HYPERLINK; // Hyperlink + } + if (preg_match('/(.*?)<\/a>/i', $v, $m)) { + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => $m[1], 'L' => '']; + $F += self::F_HYPERLINK; // mailto hyperlink + } + if (preg_match('/(.*?)<\/a>/i', $v, $m)) { + $this->sheets[$idx]['hyperlinks'][] = ['ID' => null, 'R' => $cname, 'H' => null, 'L' => $m[1]]; + $F += self::F_HYPERLINK; // internal hyperlink + } + if (preg_match('/]*)>/', $v, $m)) { + $cf = strip_tags($v); + $v = 'formula'; + if (preg_match('/ v="([^"]+)"/', $m[1], $m2)) { + $v = $m2[1]; + } + } else { + $v = strip_tags($v); + } + } // tags + $vl = mb_strlen($v); + if ($N) { + $cv = ltrim($v, '+'); + } elseif ($v === '0' || preg_match('/^[-+]?[1-9]\d{0,14}$/', $v)) { // Integer as General + $cv = ltrim($v, '+'); + if ($vl > 10) { + $N = self::N_INT; // [1] 0 + } + } elseif (preg_match('/^[-+]?(0|[1-9]\d*)\.(\d+)$/', $v, $m)) { + $cv = ltrim($v, '+'); + if (strlen($m[2]) < 3) { + $N = self::N_DEC; + } + } elseif (preg_match('/^\$[-+]?[0-9\.]+$/', $v)) { // currency $? + $N = self::N_DOLLAR; + $cv = ltrim($v, '+$'); + } elseif (preg_match('/^[-+]?[0-9\.]+( ₽| €)$/u', $v, $m)) { // currency ₽ €? + if ($m[1] === ' ₽') { + $N = self::N_RUB; + } elseif ($m[1] === ' €') { + $N = self::N_EURO; + } + $cv = trim($v, ' +₽€'); + } elseif (preg_match('/^([-+]?\d+)%$/', $v, $m)) { + $cv = round($m[1] / 100, 2); + $N = self::N_PERCENT_INT; // [9] 0% + } elseif (preg_match('/^([-+]?\d+\.\d+)%$/', $v, $m)) { + $cv = round($m[1] / 100, 4); + $N = self::N_PRECENT_DEC; // [10] 0.00% + } elseif (preg_match('/^(\d\d\d\d)-(\d\d)-(\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[1], $m[2], $m[3]); + $N = self::N_DATE; // [14] mm-dd-yy + } elseif (preg_match('/^(\d\d)\/(\d\d)\/(\d\d\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[3], $m[2], $m[1]); + $N = self::N_DATE; // [14] mm-dd-yy + } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $v, $m)) { + $cv = $this->date2excel(0, 0, 0, $m[1], $m[2], $m[3]); + $N = self::N_TIME; // time + } elseif (preg_match('/^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[1], $m[2], $m[3], $m[4], $m[5], $m[6]); + $N = ((int)$m[1] === 0) ? self::N_TIME : self::N_DATETIME; // [22] m/d/yy h:mm + } elseif (preg_match('/^(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[3], $m[2], $m[1], $m[4], $m[5], $m[6]); + $N = self::N_DATETIME; // [22] m/d/yy h:mm + } elseif (preg_match('/^[0-9+-.]+$/', $v)) { // Long ? + $A += ($A & (self::A_LEFT | self::A_CENTER)) ? 0 : self::A_RIGHT; + } elseif (preg_match('/^https?:\/\/\S+$/i', $v)) { + $h = explode('#', $v); + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => $h[0], 'L' => isset($h[1]) ? $h[1] : '']; + $F += self::F_HYPERLINK; // Hyperlink + } elseif (preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]{2,}$/", $v)) { + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => 'mailto:' . $v, 'L' => '']; + $F += self::F_HYPERLINK; // Hyperlink + } + if (($N === self::N_DATE || $N === self::N_DATETIME) && $cv < 0) { + $cv = null; + $N = 0; + } + } + if ($cv === null) { + + $v = $this->esc($v); + + if ($cf) { + $ct = 'str'; + $cv = $v; + } elseif (mb_strlen($v) > 160) { + $ct = 'inlineStr'; + $cv = $v; + } else { + $ct = 's'; // shared string + $cv = false; + $skey = '~' . $v; + if (isset($this->SI_KEYS[$skey])) { + $cv = $this->SI_KEYS[$skey]; + } + if ($cv === false) { + $this->SI[] = $v; + $cv = count($this->SI) - 1; + $this->SI_KEYS[$skey] = $cv; + } + } + } + } elseif (is_int($v)) { + $vl = mb_strlen((string)$v); + $cv = $v; + } elseif (is_float($v)) { + $vl = mb_strlen((string)$v); + $cv = $v; + } elseif ($v instanceof \DateTime) { + $vl = 16; + $cv = $this->date2excel($v->format('Y'), $v->format('m'), $v->format('d'), $v->format('H'), $v->format('i'), $v->format('s')); + $N = self::N_DATETIME; // [22] m/d/yy h:mm + } else { + continue; + } + + $COL[$CUR_COL] = max($vl, $COL[$CUR_COL]); + + $cs = 0; + + if (($N + $A + $F + $FL > 0) || $BR !== '') { + + if ($FL === self::FL_COLOR) { + $FL += self::FL_SOLID; + } + if (($F & self::F_HYPERLINK) && !($F & self::F_COLOR)) { + $F += self::F_COLOR; + $C = 'FF0563C1'; + } + + $XF_KEY = $N . '-' . $A . '-' . $F . '-' . $FL . '-' . $C . '-' . $BG . '-' . $BR; +// echo $cname .'='.$XF_KEY.PHP_EOL; + if (isset($this->XF_KEYS[$XF_KEY])) { + $cs = $this->XF_KEYS[$XF_KEY]; + } + if ($cs === 0) { + $cs = count($this->XF); + $this->XF_KEYS[$XF_KEY] = $cs; + $this->XF[] = [$N, $A, $F, $FL, $C, $BG, $BR]; + } + } + + $row .= '' + . ($cf ? '' . $cf . '' : '') + . ($ct === 'inlineStr' ? '' . $cv . '' : '' . $cv . '') . "\r\n"; + } + $ROWS[] = '' . $row . ""; + } + foreach ($COL as $k => $max) { + $w = isset($this->sheets[$idx]['colwidth'][$k]) ? $this->sheets[$idx]['colwidth'][$k] : min($max + 1, 60); + $COLS[] = ''; + } + $COLS[] = ''; + $REF = 'A1:' . $this->num2name(count($COL)) . $CUR_ROW; + } else { + $ROWS[] = '0'; + $REF = 'A1:A1'; + } + + $AUTOFILTER = ''; + if ($this->sheets[$idx]['autofilter']) { + $AUTOFILTER = ''; + } + + $MERGECELLS = []; + if (count($this->sheets[$idx]['mergecells'])) { + $MERGECELLS[] = ''; + $MERGECELLS[] = ''; + foreach ($this->sheets[$idx]['mergecells'] as $m) { + $MERGECELLS[] = ''; + } + $MERGECELLS[] = ''; + } + + $HYPERLINKS = []; + if (count($this->sheets[$idx]['hyperlinks'])) { + $HYPERLINKS[] = ''; + foreach ($this->sheets[$idx]['hyperlinks'] as $h) { + $HYPERLINKS[] = ''; + } + $HYPERLINKS[] = ''; + } + + //restore locale + setlocale(LC_NUMERIC, $_loc); + + return str_replace(['{REF}', '{COLS}', '{ROWS}', '{AUTOFILTER}', '{MERGECELLS}', '{HYPERLINKS}'], + [$REF, + implode("\r\n", $COLS), + implode("\r\n", $ROWS), + $AUTOFILTER, + implode("\r\n", $MERGECELLS), + implode("\r\n", $HYPERLINKS) + ], + $template); + } + + public function num2name($num) + { + $numeric = ($num - 1) % 26; + $letter = chr(65 + $numeric); + $num2 = (int)(($num - 1) / 26); + if ($num2 > 0) { + return $this->num2name($num2) . $letter; + } + return $letter; + } + + public function date2excel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0) + { + + $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; + + if ((int)$year === 0) { + return $excelTime; + } + + // self::CALENDAR_WINDOWS_1900 + $excel1900isLeapYear = True; + if (($year === 1900) && ($month <= 2)) { + $excel1900isLeapYear = False; + } + $myExcelBaseDate = 2415020; + + // Julian base date Adjustment + if ($month > 2) { + $month -= 3; + } else { + $month += 9; + --$year; + } + $century = substr($year, 0, 2); + $decade = substr($year, 2, 2); + // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) + $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myExcelBaseDate + $excel1900isLeapYear; + + return (float)$excelDate + $excelTime; + } + + public function setDefaultFont($name) + { + $this->defaultFont = $name; + return $this; + } + + public function setDefaultFontSize($size) + { + $this->defaultFontSize = $size; + return $this; + } + + public function autoFilter($range) + { + $this->sheets[$this->curSheet]['autofilter'] = $range; + return $this; + } + + public function mergeCells($range) + { + $this->sheets[$this->curSheet]['mergecells'][] = $range; + return $this; + } + + public function setColWidth($col, $width) + { + $this->sheets[$this->curSheet]['colwidth'][$col] = $width; + return $this; + } + + public function esc($str) + { + // XML UTF-8: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + // but we use fast version + return str_replace(['&', '<', '>', "\x00", "\x03", "\x0B"], ['&', '<', '>', '', '', ''], $str); + } + + public function getNumFmtId($code) + { + if (isset($this->NF[$code])) { // id? + return (int)$code; + } + if (isset($this->NF_KEYS[$code])) { + return $this->NF_KEYS[$code]; + } + $id = 197 + count($this->NF); // custom + $this->NF[$id] = $code; + $this->NF_KEYS[$code] = $id; + return $id; + } + + public static function raw($value) + { + return "\0" . (string)$value; + } + +} \ No newline at end of file diff --git a/spreadSheetReader/read_write_xlsx.php b/spreadSheetReader/read_write_xlsx.php new file mode 100644 index 0000000..e1f540b --- /dev/null +++ b/spreadSheetReader/read_write_xlsx.php @@ -0,0 +1,28 @@ +rows()); + } else { + echo SimpleXLSX::parseError(); + } + return $fileContent; +} + + + +function write_xlsxFile($filename, $data) { + + foreach ($data as $row) { + echo "new Row:
"; + foreach ($row as $cell) { + echo $cell." "; + } + } + + + $xlsx = \Shuchkin\SimpleXLSXGen::fromArray($data); + $xlsx->saveAs($filename); +} \ No newline at end of file