Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
<?php
function classLoader($class) {
$path = str_replace(
['\\', 'Asan' . DIRECTORY_SEPARATOR . 'PHPExcel' . DIRECTORY_SEPARATOR], [DIRECTORY_SEPARATOR, ''], $class
);
$file = __DIR__ . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $path . '.php';
if (file_exists($file)) {
require_once $file;
}
}
spl_autoload_register('classLoader');

View File

@@ -0,0 +1,12 @@
<?php
/**
* Reader Interface
*
* @author Janson
* @create 2017-11-23
*/
namespace Asan\PHPExcel\Contract;
interface ReaderInterface extends \SeekableIterator, \Countable {
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* PHP Excel
*
* @author Janson
* @create 2017-11-23
*/
namespace Asan\PHPExcel;
use Asan\PHPExcel\Exception\ReaderException;
class Excel {
/**
* Load a file
*
* @param string $file
* @param callback|null $callback
* @param string|null $encoding
* @param string $ext
* @param string $logPath
*
* @throws ReaderException
* @return \Asan\PHPExcel\Reader\BaseReader
*/
public static function load($file, $callback = null, $encoding = null, $ext = '', $logPath = '') {
set_error_handler(function($errorNo, $errorMsg, $errorFile, $errorLine) use ($logPath) {
if ($logPath) {
if (!file_exists($logPath)) {
mkdir($logPath, 0755, true);
}
$content = sprintf(
"%s\t%s.%s\t%s\t%s", date("Y-m-d H:i:s"), self::class, 'ERROR',
"[$errorNo]$errorMsg in $errorFile:$errorLine", PHP_EOL
);
file_put_contents("$logPath/excel-" . date('Y-m-d'). '.log', $content, FILE_APPEND);
}
}, E_ALL ^ E_ERROR);
$ext = $ext ?: strtolower(pathinfo($file, PATHINFO_EXTENSION));
$format = self::getFormatByExtension($ext);
if (empty($format)) {
throw new ReaderException("Could not identify file format for file [$file] with extension [$ext]");
}
$class = __NAMESPACE__ . '\\Reader\\' . $format;
$reader = new $class;
if ($callback) {
if ($callback instanceof \Closure) {
// Do the callback
call_user_func($callback, $reader);
} elseif (is_string($callback)) {
// Set the encoding
$encoding = $callback;
}
}
if ($encoding && method_exists($reader, 'setInputEncoding')) {
$reader->setInputEncoding($encoding);
}
return $reader->load($file);
}
/**
* Identify file format
*
* @param string $ext
* @return string
*/
protected static function getFormatByExtension($ext) {
$formart = '';
switch ($ext) {
/*
|--------------------------------------------------------------------------
| Excel 2007
|--------------------------------------------------------------------------
*/
case 'xlsx':
case 'xlsm':
case 'xltx':
case 'xltm':
$formart = 'Xlsx';
break;
/*
|--------------------------------------------------------------------------
| Excel5
|--------------------------------------------------------------------------
*/
case 'xls':
case 'xlt':
$formart = 'Xls';
break;
/*
|--------------------------------------------------------------------------
| CSV
|--------------------------------------------------------------------------
*/
case 'csv':
case 'txt':
$formart = 'Csv';
break;
}
return $formart;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Parser Exception
*
* @author Janson
* @create 2017-11-27
*/
namespace Asan\PHPExcel\Exception;
class ParserException extends \Exception {
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Reader Exception
*
* @author Janson
* @create 2017-11-23
*/
namespace Asan\PHPExcel\Exception;
class ReaderException extends \Exception {
}

View File

@@ -0,0 +1,839 @@
<?php
/**
* Excel2017 Parser
*
* @author Janson
* @create 2017-12-02
*/
namespace Asan\PHPExcel\Parser;
use Asan\PHPExcel\Exception\ParserException;
use Asan\PHPExcel\Exception\ReaderException;
class Excel2007 {
const CELL_TYPE_SHARED_STR = 's';
/**
* Temporary directory
*
* @var string
*/
protected $tmpDir;
/**
* ZipArchive reader
*
* @var \ZipArchive
*/
protected $zip;
/**
* Worksheet reader
*
* @var \XMLReader
*/
protected $worksheetXML;
/**
* SharedStrings reader
*
* @var \XMLReader
*/
protected $sharedStringsXML;
/**
* SharedStrings position
*
* @var array
*/
private $sharedStringsPosition = -1;
/**
* The current sheet of the file
*
* @var int
*/
private $sheetIndex = 0;
/**
* Ignore empty row
*
* @var bool
*/
private $ignoreEmpty = false;
/**
* Style xfs
*
* @var array
*/
private $styleXfs;
/**
* Number formats
*
* @var array
*/
private $formats;
/**
* Parsed number formats
*
* @var array
*/
private $parsedFormats;
/**
* Worksheets
*
* @var array
*/
private $sheets;
/**
* Default options for libxml loader
*
* @var int
*/
private static $libXmlLoaderOptions;
/**
* Base date
*
* @var \DateTime
*/
private static $baseDate;
private static $decimalSeparator = '.';
private static $thousandSeparator = ',';
private static $currencyCode = '';
private static $runtimeInfo = ['GMPSupported' => false];
/**
* Use ZipArchive reader to extract the relevant data streams from the ZipArchive file
*
* @throws ParserException|ReaderException
* @param string $file
*/
public function loadZip($file) {
$this->openFile($file);
// Setting base date
if (!self::$baseDate) {
self::$baseDate = new \DateTime;
self::$baseDate->setTimezone(new \DateTimeZone('UTC'));
self::$baseDate->setDate(1900, 1, 0);
self::$baseDate->setTime(0, 0, 0);
}
if (function_exists('gmp_gcd')) {
self::$runtimeInfo['GMPSupported'] = true;
}
}
/**
* Ignore empty row
*
* @param bool $ignoreEmpty
*
* @return $this
*/
public function ignoreEmptyRow($ignoreEmpty) {
$this->ignoreEmpty = $ignoreEmpty;
return $this;
}
/**
* Whether is ignore empty row
*
* @return bool
*/
public function isIgnoreEmptyRow() {
return $this->ignoreEmpty;
}
/**
* Set sheet index
*
* @param int $index
*
* @return $this
*/
public function setSheetIndex($index) {
if ($index != $this->sheetIndex) {
$this->sheetIndex = $index;
$this->getWorksheetXML();
}
return $this;
}
/**
* Get sheet index
*
* @return int
*/
public function getSheetIndex() {
return $this->sheetIndex;
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)
*
* @throws ReaderException
* @return array
*/
public function parseWorksheetInfo() {
if ($this->sheets === null) {
$workbookXML = simplexml_load_string(
$this->securityScan($this->zip->getFromName('xl/workbook.xml')), 'SimpleXMLElement', self::getLibXmlLoaderOptions()
);
$this->sheets = [];
if (isset($workbookXML->sheets) && $workbookXML->sheets) {
$xml = new \XMLReader();
$index = 0;
foreach ($workbookXML->sheets->sheet as $sheet) {
$info = [
'name' => (string)$sheet['name'], 'lastColumnLetter' => '', 'lastColumnIndex' => 0,
'totalRows' => 0, 'totalColumns' => 0
];
$this->zip->extractTo($this->tmpDir, $file = 'xl/worksheets/sheet' . (++$index) . '.xml');
$xml->open($this->tmpDir . '/' . $file, null, self::getLibXmlLoaderOptions());
$xml->setParserProperty(\XMLReader::DEFAULTATTRS, true);
$nonEmpty = false;
$columnLetter = '';
while ($xml->read()) {
if ($xml->name == 'row') {
if (!$this->ignoreEmpty && $xml->nodeType == \XMLReader::ELEMENT) {
$info['totalRows'] = (int)$xml->getAttribute('r');
} elseif ($xml->nodeType == \XMLReader::END_ELEMENT) {
if ($this->ignoreEmpty && $nonEmpty) {
$info['totalRows']++;
$nonEmpty = false;
}
if ($columnLetter > $info['lastColumnLetter']) {
$info['lastColumnLetter'] = $columnLetter;
}
}
} elseif ($xml->name == 'c' && $xml->nodeType == \XMLReader::ELEMENT) {
$columnLetter = preg_replace('{[^[:alpha:]]}S', '', $xml->getAttribute('r'));
} elseif ($this->ignoreEmpty && !$nonEmpty && $xml->name == 'v'
&& $xml->nodeType == \XMLReader::ELEMENT && trim($xml->readString()) !== '') {
$nonEmpty = true;
}
}
if ($info['lastColumnLetter']) {
$info['totalColumns'] = Format::columnIndexFromString($info['lastColumnLetter']);
$info['lastColumnIndex'] = $info['totalColumns'] - 1;
}
$this->sheets[] = $info;
}
$xml->close();
}
}
return $this->sheets;
}
/**
* Get shared string
*
* @param int $position
* @return string
*/
protected function getSharedString($position) {
$value = '';
$file = 'xl/sharedStrings.xml';
if ($this->sharedStringsXML === null) {
$this->sharedStringsXML = new \XMLReader();
$this->zip->extractTo($this->tmpDir, $file);
}
if ($this->sharedStringsPosition < 0 || $position < $this->sharedStringsPosition) {
$this->sharedStringsXML->open($this->tmpDir . '/' . $file, null, self::getLibXmlLoaderOptions());
$this->sharedStringsPosition = -1;
}
while ($this->sharedStringsXML->read()) {
$name = $this->sharedStringsXML->name;
$nodeType = $this->sharedStringsXML->nodeType;
if ($name == 'si') {
if ($nodeType == \XMLReader::ELEMENT) {
$this->sharedStringsPosition++;
} elseif ($position == $this->sharedStringsPosition && $nodeType == \XMLReader::END_ELEMENT) {
break;
}
} elseif ($name == 't' && $position == $this->sharedStringsPosition && $nodeType == \XMLReader::ELEMENT) {
$value .= trim($this->sharedStringsXML->readString());
}
}
return $value;
}
/**
* Parse styles info
*
* @throws ReaderException
*/
protected function parseStyles() {
if ($this->styleXfs === null) {
$stylesXML = simplexml_load_string(
$this->securityScan($this->zip->getFromName('xl/styles.xml')), 'SimpleXMLElement', self::getLibXmlLoaderOptions()
);
$this->styleXfs = $this->formats = [];
if ($stylesXML) {
if (isset($stylesXML->cellXfs->xf) && $stylesXML->cellXfs->xf) {
foreach ($stylesXML->cellXfs->xf as $xf) {
$numFmtId = isset($xf['numFmtId']) ? (int)$xf['numFmtId'] : 0;
if (isset($xf['applyNumberFormat']) || $numFmtId == 0) {
// If format ID >= 164, it is a custom format and should be read from styleSheet\numFmts
$this->styleXfs[] = $numFmtId;
} else {
// 0 for "General" format
$this->styleXfs[] = Format::FORMAT_GENERAL;
}
}
}
if (isset($stylesXML->numFmts->numFmt) && $stylesXML->numFmts->numFmt) {
foreach ($stylesXML->numFmts->numFmt as $numFmt) {
if (isset($numFmt['numFmtId'], $numFmt['formatCode'])) {
$this->formats[(int)$numFmt['numFmtId']] = (string)$numFmt['formatCode'];
}
}
}
}
}
}
/**
* Get worksheet XMLReader
*/
protected function getWorksheetXML() {
if ($this->worksheetXML === null) {
$this->worksheetXML = new \XMLReader();
}
$this->worksheetXML->open(
$this->tmpDir . '/xl/worksheets/sheet' . ($this->getSheetIndex() + 1) . '.xml',
null, self::getLibXmlLoaderOptions()
);
}
/**
* Get row data
*
* @param int $rowIndex
* @param int $columnLimit
*
* @throws ReaderException
* @return array|bool
*/
public function getRow($rowIndex, $columnLimit = 0) {
$this->parseStyles();
$rowIndex === 0 && $this->getWorksheetXML();
$sharedString = false;
$index = $styleId = 0;
$row = $columnLimit ? array_fill(0, $columnLimit, '') : [];
while ($canRead = $this->worksheetXML->read()) {
$name = $this->worksheetXML->name;
$type = $this->worksheetXML->nodeType;
// End of row
if ($name == 'row') {
if (!$this->ignoreEmpty && $type == \XMLReader::ELEMENT
&& $rowIndex+1 != (int)$this->worksheetXML->getAttribute('r')) {
$this->worksheetXML->moveToElement();
break;
}
if ($type == \XMLReader::END_ELEMENT) {
break;
}
}
if ($columnLimit > 0 && $index >= $columnLimit) {
continue;
}
switch ($name) {
// Cell
case 'c':
if ($type == \XMLReader::END_ELEMENT) {
continue;
}
$styleId = (int)$this->worksheetXML->getAttribute('s');
$letter = preg_replace('{[^[:alpha:]]}S', '', $this->worksheetXML->getAttribute('r'));
$index = Format::columnIndexFromString($letter) - 1;
// Determine cell type
$sharedString = false;
if ($this->worksheetXML->getAttribute('t') == self::CELL_TYPE_SHARED_STR) {
$sharedString = true;
}
break;
// Cell value
case 'v':
case 'is':
if ($type == \XMLReader::END_ELEMENT) {
continue;
}
$value = $this->worksheetXML->readString();
if ($sharedString) {
$value = $this->getSharedString($value);
}
// Format value if necessary
if ($value !== '' && $styleId && isset($this->styleXfs[$styleId])) {
$value = $this->formatValue($value, $styleId);
} elseif ($value && is_numeric($value)) {
$value = (float)$value;
}
$row[$index] = $value;
break;
}
}
if ($canRead === false) {
return false;
}
return $row;
}
/**
* Close ZipArchive、XMLReader and remove temp dir
*/
public function __destruct() {
if ($this->zip && $this->tmpDir) {
$this->zip->close();
}
if ($this->worksheetXML) {
$this->worksheetXML->close();
}
if ($this->sharedStringsXML) {
$this->sharedStringsXML->close();
}
$this->removeDir($this->tmpDir);
$this->zip = null;
$this->worksheetXML = null;
$this->sharedStringsXML = null;
$this->tmpDir = null;
}
/**
* Remove dir
*
* @param string $dir
*/
protected function removeDir($dir) {
if($dir && is_dir($dir)) {
$handle = opendir($dir);
while($item = readdir($handle)) {
if ($item != '.' && $item != '..') {
is_file($item = $dir . '/' . $item) ? unlink($item) : $this->removeDir($item);
}
}
closedir($handle);
rmdir($dir);
}
}
/**
* Formats the value according to the index
*
* @param string $value
* @param int $index Format index
*
* @throws \Exception
* @return string Formatted cell value
*/
private function formatValue($value, $index) {
if (!is_numeric($value)) {
return $value;
}
if (isset($this->styleXfs[$index]) && $this->styleXfs[$index] !== false) {
$index = $this->styleXfs[$index];
} else {
return $value;
}
// A special case for the "General" format
if ($index == 0) {
return is_numeric($value) ? (float)$value : $value;
}
$format = $this->parsedFormats[$index] ?? [];
if (empty($format)) {
$format = [
'code' => false, 'type' => false, 'scale' => 1, 'thousands' => false, 'currency' => false
];
if (isset(Format::$buildInFormats[$index])) {
$format['code'] = Format::$buildInFormats[$index];
} elseif (isset($this->formats[$index])) {
$format['code'] = str_replace('"', '', $this->formats[$index]);
}
// Format code found, now parsing the format
if ($format['code']) {
$sections = explode(';', $format['code']);
$format['code'] = $sections[0];
switch (count($sections)) {
case 2:
if ($value < 0) {
$format['code'] = $sections[1];
}
$value = abs($value);
break;
case 3:
case 4:
if ($value < 0) {
$format['code'] = $sections[1];
} elseif ($value == 0) {
$format['code'] = $sections[2];
}
$value = abs($value);
break;
}
}
// Stripping colors
$format['code'] = trim(preg_replace('/^\\[[a-zA-Z]+\\]/', '', $format['code']));
// Percentages
if (substr($format['code'], -1) == '%') {
$format['type'] = 'Percentage';
} elseif (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy]/i', $format['code'])) {
$format['type'] = 'DateTime';
$format['code'] = trim(preg_replace('/^(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format['code']));
$format['code'] = strtolower($format['code']);
$format['code'] = strtr($format['code'], Format::$dateFormatReplacements);
if (strpos($format['code'], 'A') === false) {
$format['code'] = strtr($format['code'], Format::$dateFormatReplacements24);
} else {
$format['code'] = strtr($format['code'], Format::$dateFormatReplacements12);
}
} elseif ($format['code'] == '[$EUR ]#,##0.00_-') {
$format['type'] = 'Euro';
} else {
// Removing skipped characters
$format['code'] = preg_replace('/_./', '', $format['code']);
// Removing unnecessary escaping
$format['code'] = preg_replace("/\\\\/", '', $format['code']);
// Removing string quotes
$format['code'] = str_replace(['"', '*'], '', $format['code']);
// Removing thousands separator
if (strpos($format['code'], '0,0') !== false || strpos($format['code'], '#,#') !== false) {
$format['thousands'] = true;
}
$format['code'] = str_replace(['0,0', '#,#'], ['00', '##'], $format['code']);
// Scaling (Commas indicate the power)
$scale = 1;
$matches = [];
if (preg_match('/(0|#)(,+)/', $format['code'], $matches)) {
$scale = pow(1000, strlen($matches[2]));
// Removing the commas
$format['code'] = preg_replace(['/0,+/', '/#,+/'], ['0', '#'], $format['code']);
}
$format['scale'] = $scale;
if (preg_match('/#?.*\?\/\?/', $format['code'])) {
$format['type'] = 'Fraction';
} else {
$format['code'] = str_replace('#', '', $format['code']);
$matches = [];
if (preg_match('/(0+)(\.?)(0*)/', preg_replace('/\[[^\]]+\]/', '', $format['code']), $matches)) {
list(, $integer, $decimalPoint, $decimal) = $matches;
$format['minWidth'] = strlen($integer) + strlen($decimalPoint) + strlen($decimal);
$format['decimals'] = $decimal;
$format['precision'] = strlen($format['decimals']);
$format['pattern'] = '%0' . $format['minWidth'] . '.' . $format['precision'] . 'f';
}
}
$matches = [];
if (preg_match('/\[\$(.*)\]/u', $format['code'], $matches)) {
$currencyCode = explode('-', $matches[1]);
if ($currencyCode) {
$currencyCode = $currencyCode[0];
}
if (!$currencyCode) {
$currencyCode = self::$currencyCode;
}
$format['currency'] = $currencyCode;
}
$format['code'] = trim($format['code']);
}
$this->parsedFormats[$index] = $format;
}
// Applying format to value
if ($format) {
if ($format['code'] == '@') {
return (string)$value;
} elseif ($format['type'] == 'Percentage') { // Percentages
if ($format['code'] === '0%') {
$value = round(100*$value, 0) . '%';
} else {
$value = sprintf('%.2f%%', round(100*$value, 2));
}
} elseif ($format['type'] == 'DateTime') { // Dates and times
$days = (int)$value;
// Correcting for Feb 29, 1900
if ($days > 60) {
$days--;
}
// At this point time is a fraction of a day
$time = ($value - (int)$value);
// Here time is converted to seconds
// Some loss of precision will occur
$seconds = $time ? (int)($time*86400) : 0;
$value = clone self::$baseDate;
$value->add(new \DateInterval('P' . $days . 'D' . ($seconds ? 'T' . $seconds . 'S' : '')));
$value = $value->format($format['code']);
} elseif ($format['type'] == 'Euro') {
$value = 'EUR ' . sprintf('%1.2f', $value);
} else {
// Fractional numbers
if ($format['type'] == 'Fraction' && ($value != (int)$value)) {
$integer = floor(abs($value));
$decimal = fmod(abs($value), 1);
// Removing the integer part and decimal point
$decimal *= pow(10, strlen($decimal) - 2);
$decimalDivisor = pow(10, strlen($decimal));
if (self::$runtimeInfo['GMPSupported']) {
$GCD = gmp_strval(gmp_gcd($decimal, $decimalDivisor));
} else {
$GCD = self::GCD($decimal, $decimalDivisor);
}
$adjDecimal = $decimal/$GCD;
$adjDecimalDivisor = $decimalDivisor/$GCD;
if (strpos($format['code'], '0') !== false || strpos($format['code'], '#') !== false
|| substr($format['code'], 0, 3) == '? ?') {
// The integer part is shown separately apart from the fraction
$value = ($value < 0 ? '-' : '') . $integer ? $integer . ' '
: '' . $adjDecimal . '/' . $adjDecimalDivisor;
} else {
// The fraction includes the integer part
$adjDecimal += $integer * $adjDecimalDivisor;
$value = ($value < 0 ? '-' : '') . $adjDecimal . '/' . $adjDecimalDivisor;
}
} else {
// Scaling
$value = $value/$format['scale'];
if (!empty($format['minWidth']) && $format['decimals']) {
if ($format['thousands']) {
$value = number_format(
$value, $format['precision'], self::$decimalSeparator, self::$thousandSeparator
);
$value = preg_replace('/(0+)(\.?)(0*)/', $value, $format['code']);
} else {
if (preg_match('/[0#]E[+-]0/i', $format['code'])) {
// Scientific format
$value = sprintf('%5.2E', $value);
} else {
$value = sprintf($format['pattern'], $value);
$value = preg_replace('/(0+)(\.?)(0*)/', $value, $format['code']);
}
}
}
}
// currency/Accounting
if ($format['currency']) {
$value = preg_replace('', $format['currency'], $value);
}
}
}
return $value;
}
/**
* Greatest common divisor calculation in case GMP extension is not enabled
*
* @param int $number1
* @param int $number2
*
* @return int
*/
private static function GCD($number1, $number2) {
$number1 = abs($number1);
$number2 = abs($number2);
if ($number1 + $number2 == 0) {
return 0;
}
$number = 1;
while ($number1 > 0) {
$number = $number1;
$number1 = $number2 % $number1;
$number2 = $number;
}
return $number;
}
/**
* Open file for reading
*
* @param string $file
*
* @throws ParserException|ReaderException
*/
public function openFile($file) {
// Check if file exists
if (!file_exists($file) || !is_readable($file)) {
throw new ReaderException("Could not open file [$file] for reading! File does not exist.");
}
$this->zip = new \ZipArchive();
$xl = false;
if ($this->zip->open($file) === true) {
$this->tmpDir = sys_get_temp_dir() . '/' . uniqid();
// check if it is an OOXML archive
$rels = simplexml_load_string(
$this->securityScan($this->zip->getFromName('_rels/.rels')),
'SimpleXMLElement', self::getLibXmlLoaderOptions()
);
if ($rels !== false) {
foreach ($rels->Relationship as $rel) {
switch ($rel["Type"]) {
case "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument":
if ($rel["Target"] == 'xl/workbook.xml') {
$xl = true;
}
break;
}
}
}
}
if ($xl === false) {
throw new ParserException("The file [$file] is not recognised as a zip archive");
}
}
/**
* Scan theXML for use of <!ENTITY to prevent XXE/XEE attacks
*
* @param string $xml
*
* @throws ReaderException
* @return string
*/
protected function securityScan($xml) {
$pattern = sprintf('/\\0?%s\\0?/', implode('\\0?', str_split('<!DOCTYPE')));
if (preg_match($pattern, $xml)) {
throw new ReaderException(
'Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'
);
}
return $xml;
}
/**
* Set default options for libxml loader
*
* @param int $options Default options for libxml loader
*/
public static function setLibXmlLoaderOptions($options = null) {
if (is_null($options) && defined(LIBXML_DTDLOAD)) {
$options = LIBXML_DTDLOAD | LIBXML_DTDATTR;
}
if (version_compare(PHP_VERSION, '5.2.11') >= 0) {
@libxml_disable_entity_loader($options == (LIBXML_DTDLOAD | LIBXML_DTDATTR));
}
self::$libXmlLoaderOptions = $options;
}
/**
* Get default options for libxml loader.
* Defaults to LIBXML_DTDLOAD | LIBXML_DTDATTR when not set explicitly.
*
* @return int Default options for libxml loader
*/
public static function getLibXmlLoaderOptions() {
if (is_null(self::$libXmlLoaderOptions) && defined(LIBXML_DTDLOAD)) {
self::setLibXmlLoaderOptions(LIBXML_DTDLOAD | LIBXML_DTDATTR);
}
if (version_compare(PHP_VERSION, '5.2.11') >= 0) {
@libxml_disable_entity_loader(self::$libXmlLoaderOptions == (LIBXML_DTDLOAD | LIBXML_DTDATTR));
}
return self::$libXmlLoaderOptions;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,276 @@
<?php
/**
* OLE File Read
*
* @author Janson
* @create 2017-11-27
*/
namespace Asan\PHPExcel\Parser\Excel5;
use Asan\PHPExcel\Exception\ParserException;
use Asan\PHPExcel\Exception\ReaderException;
use Asan\PHPExcel\Parser\Format;
defined('IDENTIFIER_OLE') ||
define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1));
class OLERead {
// OLE identifier
const IDENTIFIER_OLE = IDENTIFIER_OLE;
// Size of a sector = 512 bytes
const BIG_BLOCK_SIZE = 0x200;
// Size of a short sector = 64 bytes
const SMALL_BLOCK_SIZE = 0x40;
// Size of a directory entry always = 128 bytes
const PROPERTY_STORAGE_BLOCK_SIZE = 0x80;
// Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
const SMALL_BLOCK_THRESHOLD = 0x1000;
// header offsets
const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c;
const ROOT_START_BLOCK_POS = 0x30;
const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c;
const EXTENSION_BLOCK_POS = 0x44;
const NUM_EXTENSION_BLOCK_POS = 0x48;
const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c;
// property storage offsets (directory offsets)
const SIZE_OF_NAME_POS = 0x40;
const TYPE_POS = 0x42;
const START_BLOCK_POS = 0x74;
const SIZE_POS = 0x78;
public $workbook = null;
public $summaryInformation = null;
public $documentSummaryInformation = null;
protected $data = '';
protected $bigBlockChain = '';
protected $smallBlockChain = '';
protected $entry = '';
protected $props = [];
protected $rootEntry = 0;
protected $sbdStartBlock = 0;
protected $extensionBlock = 0;
protected $rootStartBlock = 0;
protected $numExtensionBlocks = 0;
protected $numBigBlockDepotBlocks = 0;
/**
* Read the file
*
* @throws ParserException|ReaderException
* @param string $file
*/
public function read($file) {
$this->openFile($file);
// Total number of sectors used for the SAT
$this->numBigBlockDepotBlocks = Format::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
// SecID of the first sector of the directory stream
$this->rootStartBlock = Format::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
// SecID of the first sector of the SSAT (or -2 if not extant)
$this->sbdStartBlock = Format::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
// SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
$this->extensionBlock = Format::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
// Total number of sectors used by MSAT
$this->numExtensionBlocks = Format::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
$bigBlockDepotBlocks = [];
$pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
$bbdBlocks = $this->numBigBlockDepotBlocks;
if ($this->numExtensionBlocks != 0) {
$bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
}
for ($i = 0; $i < $bbdBlocks; ++$i) {
$bigBlockDepotBlocks[$i] = Format::getInt4d($this->data, $pos);
$pos += 4;
}
for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
$pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
$blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
$bigBlockDepotBlocks[$i] = Format::getInt4d($this->data, $pos);
$pos += 4;
}
$bbdBlocks += $blocksToRead;
if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
$this->extensionBlock = Format::getInt4d($this->data, $pos);
}
}
$this->bigBlockChain = '';
$bbs = self::BIG_BLOCK_SIZE / 4;
for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
$pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
$this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs);
}
$sbdBlock = $this->sbdStartBlock;
$this->smallBlockChain = '';
while ($sbdBlock != -2) {
$pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
$this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
$sbdBlock = Format::getInt4d($this->bigBlockChain, $sbdBlock * 4);
}
// read the directory stream
$block = $this->rootStartBlock;
$this->entry = $this->readData($block);
$this->readPropertySets();
}
/**
* Open file for reading
*
* @param string $file
*
* @throws ReaderException|ParserException
*/
public function openFile($file) {
// Check if file exists
if (!file_exists($file) || !is_readable($file)) {
throw new ReaderException("Could not open file [$file] for reading! File does not exist.");
}
// Get the file data
$this->data = file_get_contents($file);
// Check OLE identifier
if (empty($this->data) || substr($this->data, 0, 8) != self::IDENTIFIER_OLE) {
throw new ParserException("The file [$file] is not recognised as an OLE file");
}
}
/**
* Extract binary stream data.
*
* @param int $stream
*
* @return string|null
*/
public function getStream($stream) {
if ($stream === null) {
return null;
}
$streamData = '';
if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
$rootData = $this->readData($this->props[$this->rootEntry]['startBlock']);
$block = $this->props[$stream]['startBlock'];
while ($block != -2) {
$pos = $block * self::SMALL_BLOCK_SIZE;
$streamData .= substr($rootData, $pos, self::SMALL_BLOCK_SIZE);
$block = Format::getInt4d($this->smallBlockChain, $block * 4);
}
return $streamData;
}
$numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
++$numBlocks;
}
if ($numBlocks == 0) {
return '';
}
$block = $this->props[$stream]['startBlock'];
while ($block != -2) {
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
$streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
$block = Format::getInt4d($this->bigBlockChain, $block * 4);
}
return $streamData;
}
/**
* Read a standard stream (by joining sectors using information from SAT).
*
* @param int $bl Sector ID where the stream starts
*
* @return string
*/
protected function readData($bl) {
$block = $bl;
$data = '';
while ($block != -2) {
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
$data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
$block = Format::getInt4d($this->bigBlockChain, $block * 4);
}
return $data;
}
/**
* Read entries in the directory stream.
*/
protected function readPropertySets() {
$offset = 0;
// loop through entires, each entry is 128 bytes
$entryLen = strlen($this->entry);
while ($offset < $entryLen) {
// entry data (128 bytes)
$d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
// size in bytes of name
$nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8);
// type of entry
$type = ord($d[self::TYPE_POS]);
// sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
// sectorID of first sector of the short-stream container stream, if this entry is root entry
$startBlock = Format::getInt4d($d, self::START_BLOCK_POS);
$size = Format::getInt4d($d, self::SIZE_POS);
$name = str_replace("\x00", '', substr($d, 0, $nameSize));
$this->props[] = [
'name' => $name,
'type' => $type,
'startBlock' => $startBlock,
'size' => $size,
];
// tmp helper to simplify checks
$upName = strtoupper($name);
// Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
$this->workbook = count($this->props) - 1;
} elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
// Root entry
$this->rootEntry = count($this->props) - 1;
}
// Summary information
if ($name == chr(5) . 'SummaryInformation') {
$this->summaryInformation = count($this->props) - 1;
}
// Additional Document Summary information
if ($name == chr(5) . 'DocumentSummaryInformation') {
$this->documentSummaryInformation = count($this->props) - 1;
}
$offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* Excel5 RC4
*
* @author Janson
* @create 2017-11-29
*/
namespace Asan\PHPExcel\Parser\Excel5;
class RC4 {
// Context
private $s = [];
private $i = 0;
private $j = 0;
/**
* RC4 stream decryption/encryption constrcutor
*
* @param string $key Encryption key/passphrase
*/
public function __construct($key) {
$len = strlen($key);
for ($this->i = 0; $this->i < 256; $this->i++) {
$this->s[$this->i] = $this->i;
}
$this->j = 0;
for ($this->i = 0; $this->i < 256; $this->i++) {
$this->j = ($this->j + $this->s[$this->i] + ord($key[$this->i % $len])) % 256;
$t = $this->s[$this->i];
$this->s[$this->i] = $this->s[$this->j];
$this->s[$this->j] = $t;
}
$this->i = $this->j = 0;
}
/**
* Symmetric decryption/encryption function
*
* @param string $data Data to encrypt/decrypt
*
* @return string
*/
public function RC4($data) {
$len = strlen($data);
for ($c = 0; $c < $len; $c++) {
$this->i = ($this->i + 1) % 256;
$this->j = ($this->j + $this->s[$this->i]) % 256;
$t = $this->s[$this->i];
$this->s[$this->i] = $this->s[$this->j];
$this->s[$this->j] = $t;
$t = ($this->s[$this->i] + $this->s[$this->j]) % 256;
$data[$c] = chr(ord($data[$c]) ^ $this->s[$t]);
}
return $data;
}
}

View File

@@ -0,0 +1,278 @@
<?php
/**
* Format helper
*
* @author Janson
* @create 2017-11-27
*/
namespace Asan\PHPExcel\Parser;
use Asan\PHPExcel\Exception\ParserException;
class Format {
//Base date of 1st Jan 1900 = 1.0
const CALENDAR_WINDOWS_1900 = 1900;
//Base date of 2nd Jan 1904 = 1.0
const CALENDAR_MAC_1904 = 1904;
// Pre-defined formats
const FORMAT_GENERAL = 'General';
const FORMAT_TEXT = '@';
const FORMAT_PERCENTAGE = '0%';
const FORMAT_PERCENTAGE_00 = '0.00%';
const FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-';
public static $buildInFormats = [
0 => self::FORMAT_GENERAL,
1 => '0',
2 => '0.00',
3 => '#,##0',
4 => '#,##0.00',
5 => '"$"#,##0_),("$"#,##0)',
6 => '"$"#,##0_),[Red]("$"#,##0)',
7 => '"$"#,##0.00_),("$"#,##0.00)',
8 => '"$"#,##0.00_),[Red]("$"#,##0.00)',
9 => '0%',
10 => '0.00%',
//11 => '0.00E+00',
12 => '# ?/?',
13 => '# ??/??',
14 => 'yyyy/m/d',
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 => 'yyyy/m/d h:mm',
// 补充
28 => 'm月d日',
31 => 'yyyy年m月d日',
32 => 'h时i分',
33 => 'h时i分ss秒',
34 => 'AM/PM h时i分',
35 => 'AM/PM h时i分ss秒',
55 => 'AM/PM h时i分',
56 => 'AM/PM h时i分ss秒',
58 => 'm月d日',
37 => '#,##0_),(#,##0)',
38 => '#,##0_),[Red](#,##0)',
39 => '#,##0.00_),(#,##0.00)',
40 => '#,##0.00_),[Red](#,##0.00)',
41 => '_("$"* #,##0_),_("$"* (#,##0),_("$"* "-"_),_(@_)',
42 => '_(* #,##0_),_(* (#,##0),_(* "-"_),_(@_)',
43 => '_(* #,##0.00_),_(* (#,##0.00),_(* "-"??_),_(@_)',
44 => '_("$"* #,##0.00_),_("$"* \(#,##0.00\),_("$"* "-"??_),_(@_)',
45 => 'mm:ss',
46 => '[h]:mm:ss',
47 => 'mm:ss.0',
48 => '##0.0E+0',
49 => '@',
// CHT
27 => 'yyyy年m月',
30 => 'm/d/yy',
36 => '[$-404]e/m/d',
50 => '[$-404]e/m/d',
57 => 'yyyy年m月',
// THA
59 => 't0',
60 => 't0.00',
61 => 't#,##0',
62 => 't#,##0.00',
67 => 't0%',
68 => 't0.00%',
69 => 't# ?/?',
70 => 't# ??/??'
];
/**
* Search/replace values to convert Excel date/time format masks to PHP format masks
*
* @var array
*/
public static $dateFormatReplacements = [
// first remove escapes related to non-format characters
'\\' => '',
// 12-hour suffix
'am/pm' => 'A',
// 2-digit year
'e' => 'Y',
'yyyy' => 'Y',
'yy' => 'y',
// first letter of month - no php equivalent
'mmmmm' => 'M',
// full month name
'mmmm' => 'F',
// short month name
'mmm' => 'M',
// mm is minutes if time, but can also be month w/leading zero
// so we try to identify times be the inclusion of a : separator in the mask
// It isn't perfect, but the best way I know how
':mm' => ':i',
'mm:' => 'i:',
// month leading zero
'mm' => 'm',
'm' => 'n',
// full day of week name
'dddd' => 'l',
// short day of week name
'ddd' => 'D',
// days leading zero
'dd' => 'd',
'd' => 'j',
// seconds
'ss' => 's',
// fractional seconds - no php equivalent
'.s' => ''
];
/**
* Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock)
*
* @var array
*/
public static $dateFormatReplacements24 = [
'hh' => 'H',
'h' => 'G'
];
/**
* Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock)
*
* @var array
*/
public static $dateFormatReplacements12 = [
'hh' => 'h',
'h' => 'g'
];
/**
* Column index from string
*
* @param string $label
*
* @throws \Exception
* @return int
*/
public static function columnIndexFromString($label = 'A') {
// Using a lookup cache adds a slight memory overhead, but boosts speed
// caching using a static within the method is faster than a class static,
// though it's additional memory overhead
static $indexCache = [];
if (isset($indexCache[$label])) {
return $indexCache[$label];
}
// It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather
// than use ord() and make it case insensitive to get rid of the strtoupper() as well. Because it's a static,
// there's no significant memory overhead either
static $columnLookup = [
'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10,
'K' => 11, 'L' => 12, 'M' => 13, 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19,
'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26, 'a' => 1, 'b' => 2, 'c' => 3,
'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13,
'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22,
'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26
];
// We also use the language construct isset() rather than the more costly strlen() function to match the length
// of $pString for improved performance
if (!isset($indexCache[$label])) {
if (!isset($label[0]) || isset($label[3])) {
throw new ParserException('Column string can not be empty or longer than 3 characters');
}
if (!isset($label[1])) {
$indexCache[$label] = $columnLookup[$label];
} elseif (!isset($label[2])) {
$indexCache[$label] = $columnLookup[$label[0]] * 26 + $columnLookup[$label[1]];
} else {
$indexCache[$label] = $columnLookup[$label[0]] * 676 + $columnLookup[$label[1]] * 26
+ $columnLookup[$label[2]];
}
}
return $indexCache[$label];
}
/**
* String from columnindex
*
* @param int $column
* @return string
*/
public static function stringFromColumnIndex($column = 0) {
// Using a lookup cache adds a slight memory overhead, but boosts speed
// caching using a static within the method is faster than a class static,
// though it's additional memory overhead
static $stringCache = [];
if (!isset($stringCache[$column])) {
// Determine column string
if ($column < 26) {
$stringCache[$column] = chr(65 + $column);
} elseif ($column < 702) {
$stringCache[$column] = chr(64 + ($column / 26)) . chr(65 + $column % 26);
} else {
$stringCache[$column] = chr(64 + (($column - 26) / 676)) . chr(65 + ((($column - 26) % 676) / 26))
. chr(65 + $column % 26);
}
}
return $stringCache[$column];
}
/**
* Read 16-bit unsigned integer
*
* @param string $data
* @param int $pos
* @return int
*/
public static function getUInt2d($data, $pos) {
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
}
/**
* Read 32-bit signed integer
*
* @param string $data
* @param int $pos
* @return int
*/
public static function getInt4d($data, $pos) {
// FIX: represent numbers correctly on 64-bit system
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
// Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
$ord24 = ord($data[$pos + 3]);
if ($ord24 >= 128) {
// negative number
$ord24 = -abs((256 - $ord24) << 24);
} else {
$ord24 = ($ord24 & 127) << 24;
}
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24;
}
}

View File

@@ -0,0 +1,162 @@
<?php
/**
* Reader Abstract
*
* @author Janson
* @create 2017-11-23
*/
namespace Asan\PHPExcel\Reader;
use Asan\PHPExcel\Contract\ReaderInterface;
abstract class BaseReader implements ReaderInterface {
/**
* Generator
*
* @var \Generator
*/
protected $generator;
/**
* File row count
*
* @var int
*/
protected $count;
/**
* Max row number
*
* @var int
*/
protected $rowLimit;
/**
* Max column number
*
* @var int
*/
protected $columnLimit;
/**
* Return the current element
*
* @return array
*/
public function current() {
return $this->generator->current();
}
/**
* Move forward to next element
*/
public function next() {
$this->generator->next();
}
/**
* Return the key of the current element
*
* @return int
*/
public function key() {
return $this->generator->key();
}
/**
* Checks if current position is valid
*
* @return bool
*/
public function valid() {
return $this->generator->valid();
}
/**
* Rewind the Iterator to the first element
*/
public function rewind() {
$this->generator = $this->makeGenerator();
}
/**
* Make the generator
*/
protected function makeGenerator() {
}
/**
* Ignore empty row
*
* @param bool $ignoreEmpty
*/
public function ignoreEmptyRow($ignoreEmpty = false) {
}
/**
* Set row limit
*
* @param int $limit
* @return $this
*/
public function setRowLimit($limit = null) {
$this->rowLimit = $limit;
return $this;
}
/**
* Get row limit
*
* @return int
*/
public function getRowLimit() {
return $this->rowLimit;
}
/**
* Set column limit
*
* @param int $limit
* @return $this
*/
public function setColumnLimit($limit = null) {
$this->columnLimit = $limit;
return $this;
}
/**
* Takes a row and traverses the file to that row
*
* @param int $row
*/
public function seek($row) {
if ($row <= 0) {
throw new \InvalidArgumentException("Row $row is invalid");
}
$key = $this->key();
if ($key !== --$row) {
if ($row < $key || is_null($key) || $row == 0) {
$this->rewind();
}
while ($this->valid() && $row > $this->key()) {
$this->next();
}
}
}
/**
* Get column limit
*
* @return int
*/
public function getColumnLimit() {
return $this->columnLimit;
}
}

View File

@@ -0,0 +1,319 @@
<?php
/**
* Csv Reader
*
* @author Janson
* @create 2017-11-23
*/
namespace Asan\PHPExcel\Reader;
use Asan\PHPExcel\Exception\ReaderException;
class Csv extends BaseReader {
/**
* File handle
*
* @var resource
*/
protected $fileHandle;
/**
* File read start
*
* @var int
*/
protected $start = 0;
/**
* Input encoding
*
* @var string
*/
protected $inputEncoding;
/**
* Delimiter
*
* @var string
*/
protected $delimiter;
/**
* Enclosure
*
* @var string
*/
protected $enclosure = '"';
/**
* Ignore empty row
*
* @var bool
*/
protected $ignoreEmpty = false;
/**
* Loads Excel from file
*
* @param string $file
*
* @throws ReaderException
* @return $this
*/
public function load($file) {
$lineEnding = ini_get('auto_detect_line_endings');
ini_set('auto_detect_line_endings', true);
// Open file
$this->openFile($file);
$this->autoDetection();
$this->generator = $this->makeGenerator();
ini_set('auto_detect_line_endings', $lineEnding);
return $this;
}
/**
* Count elements of the selected sheet
*
* @return int
*/
public function count() {
if ($this->count === null) {
$position = ftell($this->fileHandle);
$this->count = iterator_count($this->makeGenerator(true));
fseek($this->fileHandle, $position);
}
return $this->count;
}
/**
* Make the generator
*
* @param bool $calculate
* @return \Generator
*/
protected function makeGenerator($calculate = false) {
fseek($this->fileHandle, $this->start);
$finish = 0;
while (($row = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure)) !== false) {
if ($this->ignoreEmpty && (empty($row) || trim(implode('', $row)) === '')) {
continue;
}
if ($calculate) {
yield;
continue;
}
if ($this->rowLimit > 0 && ++$finish > $this->rowLimit) {
break;
}
if ($this->columnLimit > 0) {
$row = array_slice($row, 0, $this->columnLimit);
}
foreach ($row as &$value) {
if ($value != '') {
if (is_numeric($value)) {
$value = (float)$value;
}
// Convert encoding if necessary
if ($this->inputEncoding !== 'UTF-8') {
$value = mb_convert_encoding($value, 'UTF-8', $this->inputEncoding);
}
}
}
unset($value);
yield $row;
}
}
/**
* Detect the file delimiter and encoding
*/
protected function autoDetection() {
if (($this->delimiter !== null && $this->inputEncoding !== null)
|| ($line = fgets($this->fileHandle)) === false) {
return;
}
if ($this->delimiter === null) {
$this->delimiter = ',';
if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) {
$this->delimiter = substr($line, 4, 1);
}
}
if ($this->inputEncoding === null) {
$this->inputEncoding = 'UTF-8';
if (($bom = substr($line, 0, 4)) == "\xFF\xFE\x00\x00" || $bom == "\x00\x00\xFE\xFF") {
$this->start = 4;
$this->inputEncoding = 'UTF-32';
} elseif (($bom = substr($line, 0, 2)) == "\xFF\xFE" || $bom == "\xFE\xFF") {
$this->start = 2;
$this->inputEncoding = 'UTF-16';
} elseif (($bom = substr($line, 0, 3)) == "\xEF\xBB\xBF") {
$this->start = 3;
}
if (!$this->start) {
$encoding = mb_detect_encoding($line, 'ASCII, UTF-8, GB2312, GBK');
if ($encoding) {
if ($encoding == 'EUC-CN') {
$encoding = 'GB2312';
} elseif ($encoding == 'CP936') {
$encoding = 'GBK';
}
$this->inputEncoding = $encoding;
}
}
}
fseek($this->fileHandle, $this->start);
}
/**
* Ignore empty row
*
* @param bool $ignoreEmpty
*
* @return $this
*/
public function ignoreEmptyRow($ignoreEmpty = false) {
$this->ignoreEmpty = $ignoreEmpty;
return $this;
}
/**
* Set input encoding
*
* @param string $encoding
* @return $this
*/
public function setInputEncoding($encoding = 'UTF-8') {
$this->inputEncoding = $encoding;
return $this;
}
/**
* Get input encoding
*
* @return string
*/
public function getInputEncoding() {
return $this->inputEncoding;
}
/**
* Set delimiter
*
* @param string $delimiter Delimiter, defaults to ,
* @return $this
*/
public function setDelimiter($delimiter = ',') {
$this->delimiter = $delimiter;
return $this;
}
/**
* Get delimiter
*
* @return string
*/
public function getDelimiter() {
return $this->delimiter;
}
/**
* Set enclosure
*
* @param string $enclosure Enclosure, defaults to "
* @return $this
*/
public function setEnclosure($enclosure = '"') {
if ($enclosure == '') {
$enclosure = '"';
}
$this->enclosure = $enclosure;
return $this;
}
/**
* Get enclosure
*
* @return string
*/
public function getEnclosure() {
return $this->enclosure;
}
/**
* Can the current Reader read the file?
*
* @param string $file
*
* @return bool
*/
public function canRead($file) {
try {
$this->openFile($file);
} catch (\Exception $e) {
return false;
}
fclose($this->fileHandle);
return true;
}
/**
* Open file for reading
*
* @param string $file
*
* @throws ReaderException
*/
protected function openFile($file) {
// Check if file exists
if (!file_exists($file) || !is_readable($file)) {
throw new ReaderException("Could not open file [$file] for reading! File does not exist.");
}
// Open file
$this->fileHandle = fopen($file, 'r');
if ($this->fileHandle === false) {
throw new ReaderException("Could not open file [$file] for reading.");
}
}
/**
* Close file and release generator
*/
public function __destruct() {
if ($this->fileHandle) {
fclose($this->fileHandle);
}
$this->generator = null;
}
}

View File

@@ -0,0 +1,163 @@
<?php
/**
* Xls Reader
*
* @author Janson
* @create 2017-11-23
*/
namespace Asan\PHPExcel\Reader;
use Asan\PHPExcel\Parser\Excel5;
use Asan\PHPExcel\Parser\Excel5\OLERead;
class Xls extends BaseReader {
/**
* Xls parser
*
* @var Excel5
*/
protected $parser;
/**
* File row、column count
*
* @var array|int
*/
protected $count;
public function __construct() {
$this->parser = new Excel5();
}
/**
* Loads Excel from file
*
* @param string $file
*
* @return $this
*/
public function load($file) {
$this->parser->loadOLE($file);
$this->generator = $this->makeGenerator();
return $this;
}
/**
* Count elements of the selected sheet
*
* @param bool $all
* @return int|array
*/
public function count($all = false) {
if ($this->count === null) {
$row = $column = 0;
if ($sheet = $this->sheets($this->parser->getSheetIndex())) {
$row = $sheet['totalRows'] ?? 0;
$column = $sheet['totalColumns'] ?? 0;
}
$this->count = [
$this->rowLimit > 0 ? min($row, $this->rowLimit) : $row,
$this->columnLimit > 0 ? min($column, $this->columnLimit) : $column
];
}
return $all ? $this->count : $this->count[0];
}
/**
* Get the work sheets info
*
* @param int $index
* @return array
*/
public function sheets($index = null) {
$sheets = $this->parser->parseWorksheetInfo();
if ($index !== null) {
return $sheets[$index] ?? [];
}
return $sheets;
}
/**
* Make the generator
*
* @return \Generator
*/
protected function makeGenerator() {
list($rowLimit, $columnLimit) = $this->count(true);
$line = $finish = 0;
while ($finish < $rowLimit && ($row = $this->parser->getRow($line++, $columnLimit)) !== false) {
if ($this->parser->isIgnoreEmptyRow() && trim(implode('', $row)) === '') {
continue;
}
$finish++;
yield $row;
}
}
/**
* Ignore empty row
*
* @param bool $ignoreEmpty
*
* @return $this
*/
public function ignoreEmptyRow($ignoreEmpty = false) {
$this->parser->ignoreEmptyRow($ignoreEmpty);
return $this;
}
/**
* Set sheet index
*
* @param int $index
* @return $this
*/
public function setSheetIndex($index) {
if ($index != $this->parser->getSheetIndex()) {
$this->parser->setSheetIndex($index);
$this->count = null;
$this->rewind();
}
return $this;
}
/**
* Can the current Reader read the file?
*
* @param string $file
*
* @return bool
*/
public function canRead($file) {
try {
// Use ParseXL for the hard work.
$ole = new OLERead();
// open file
$ole->openFile($file);
} catch (\Exception $e) {
return false;
}
return true;
}
/**
* Release parser and generator
*/
public function __destruct() {
$this->parser = null;
$this->generator = null;
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* Xlsx Reader
*
* @author Janson
* @create 2017-11-23
*/
namespace Asan\PHPExcel\Reader;
use Asan\PHPExcel\Parser\Excel2007;
class Xlsx extends BaseReader {
/**
* Xls parser
*
* @var Excel2007
*/
protected $parser;
/**
* File row、column count
*
* @var array|int
*/
protected $count;
public function __construct() {
$this->parser = new Excel2007();
}
/**
* Loads Excel from file
*
* @param string $file
*
* @return $this
*/
public function load($file) {
$this->parser->loadZip($file);
$this->generator = $this->makeGenerator();
return $this;
}
/**
* Count elements of an object
*
* @param bool $all
* @return int|array
*/
public function count($all = false) {
if ($this->count === null) {
$row = $column = 0;
if ($sheet = $this->sheets($this->parser->getSheetIndex())) {
$row = $sheet['totalRows'] ?? 0;
$column = $sheet['totalColumns'] ?? 0;
}
$this->count = [
$this->rowLimit > 0 ? min($row, $this->rowLimit) : $row,
$this->columnLimit > 0 ? min($column, $this->columnLimit) : $column
];
}
return $all ? $this->count : $this->count[0];
}
/**
* Get the work sheets info
*
* @param int $index
* @return array
*/
public function sheets($index = null) {
$sheets = $this->parser->parseWorksheetInfo();
if ($index !== null) {
return $sheets[$index] ?? [];
}
return $sheets;
}
/**
* Make the generator
*
* @return \Generator
*/
protected function makeGenerator() {
list($rowLimit, $columnLimit) = $this->count(true);
$line = $finish = 0;
while ($finish < $rowLimit && ($row = $this->parser->getRow($line++, $columnLimit)) !== false) {
if ($this->parser->isIgnoreEmptyRow() && trim(implode('', $row)) === '') {
continue;
}
$finish++;
yield $row;
}
}
/**
* Ignore empty row
*
* @param bool $ignoreEmpty
*
* @return $this
*/
public function ignoreEmptyRow($ignoreEmpty = false) {
$this->parser->ignoreEmptyRow($ignoreEmpty);
return $this;
}
/**
* Set sheet index
*
* @param int $index
* @return $this
*/
public function setSheetIndex($index = 0) {
if ($index != $this->parser->getSheetIndex()) {
$this->parser->setSheetIndex($index);
$this->count = null;
$this->rewind();
}
return $this;
}
/**
* Can the current Reader read the file?
*
* @param string $file
*
* @return bool
*/
public function canRead($file) {
try {
$parser = new Excel2007();
// open file
$parser->openFile($file);
} catch (\Exception $e) {
return false;
}
return true;
}
/**
* Release parser and generator
*/
public function __destruct() {
$this->parser = null;
$this->generator = null;
}
}