<?php
namespace app\adminVue\controller;

/** Reads the existing intranet XLSX export without loading worksheets into memory. */
class V2InventoryFile
{
    public static function sheets($path) {
        $zip=new \ZipArchive();
        if($zip->open($path)!==true)throw new \InvalidArgumentException('无法读取xlsx文件');
        try {
            $size=0;
            for($i=0;$i<$zip->numFiles;$i++){$s=$zip->statIndex($i);$size+=$s['size'];if($size>512*1024*1024)throw new \InvalidArgumentException('Excel解压后超过512MB');}
            $xml=self::xml($zip->getFromName('xl/workbook.xml'));
            $rels=self::xml($zip->getFromName('xl/_rels/workbook.xml.rels'));$targets=[];
            foreach($rels->children() as $r)$targets[(string)$r['Id']]=(string)$r['Target'];
            $out=[];
            foreach($xml->xpath('//*[local-name()="sheet"]') as $s){
                $a=$s->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');$target=$targets[(string)$a['id']]??'';
                $target=strpos($target,'/')===0?ltrim($target,'/'):'xl/'.$target;
                if(strpos($target,'..')!==false||strpos($target,'xl/worksheets/')!==0||!$zip->statName($target))throw new \InvalidArgumentException('Excel工作表路径无效');
                $name=(string)$s['name'];if(isset($out[$name]))throw new \InvalidArgumentException('Excel工作表重名');$out[$name]=$target;
            }
            return $out;
        } finally {$zip->close();}
    }
    private static function xml($text) {
        if(!is_string($text)||stripos($text,'<!DOCTYPE')!==false||stripos($text,'<!ENTITY')!==false)throw new \InvalidArgumentException('Excel XML格式无效');
        $old=libxml_use_internal_errors(true);$xml=simplexml_load_string($text,'SimpleXMLElement',LIBXML_NONET);libxml_clear_errors();libxml_use_internal_errors($old);
        if(!$xml)throw new \InvalidArgumentException('Excel XML损坏');return $xml;
    }
    public static function strings($path) {
        $zip=new \ZipArchive();$zip->open($path);$has=$zip->statName('xl/sharedStrings.xml');$zip->close();if(!$has)return [];
        $r=self::reader($path,'xl/sharedStrings.xml');$out=[];
        try{while($r->read()){self::safeNode($r);if($r->nodeType===\XMLReader::ELEMENT&&$r->localName==='si'){$s=self::xml($r->readOuterXml());$text='';foreach($s->xpath('//*[local-name()="t"]') as $t)$text.=(string)$t;$out[]=$text;if(count($out)>2000000)throw new \InvalidArgumentException('共享文本过多');}}}finally{$r->close();}return $out;
    }
    private static function reader($path,$entry) {
        $r=new \XMLReader();if(!$r->open('zip://'.str_replace('\\','/',$path).'#'.$entry,null,LIBXML_NONET))throw new \InvalidArgumentException('无法读取工作表');return $r;
    }
    private static function safeNode($r) {if(in_array($r->nodeType,[\XMLReader::DOC_TYPE,\XMLReader::ENTITY,\XMLReader::ENTITY_REF],true))throw new \InvalidArgumentException('Excel不允许包含实体或DTD');}
    public static function rows($path,$entry,$strings) {
        $r=self::reader($path,$entry);$number=0;$old=libxml_use_internal_errors(true);
        try {
            while($r->read()){
                self::safeNode($r);if($r->nodeType!==\XMLReader::ELEMENT||$r->localName!=='row')continue;
                $number++;$number=(int)($r->getAttribute('r')?:$number);$row=self::xml($r->readOuterXml());$values=[];$next=0;
                foreach($row->xpath('./*[local-name()="c"]') as $c){
                    if($c->xpath('./*[local-name()="f"]'))throw new \InvalidArgumentException('第'.$number.'行包含公式，请在内网导出纯数据');
                    $col=$next;if(preg_match('/^([A-Z]+)[0-9]+$/',(string)$c['r'],$m)){$col=0;foreach(str_split($m[1]) as $ch)$col=$col*26+ord($ch)-64;$col--;}
                    if($col>50)throw new \InvalidArgumentException('Excel列数超过限制');$next=$col+1;
                    $v=$c->xpath('./*[local-name()="v"]');$value=$v?(string)$v[0]:'';$type=(string)$c['t'];
                    if($type==='s'){if(!ctype_digit($value)||!array_key_exists((int)$value,$strings))throw new \InvalidArgumentException('共享文本索引损坏');$value=$strings[(int)$value];}
                    elseif($type==='inlineStr'){$value='';foreach($c->xpath('.//*[local-name()="t"]') as $t)$value.=(string)$t;}
                    elseif($type==='e')throw new \InvalidArgumentException('第'.$number.'行有Excel错误值');
                    $values[$col]=$value;
                }
                if(array_filter($values,function($v){return $v!=='';}))yield $number=>$values;
            }
            if(libxml_get_errors())throw new \InvalidArgumentException('Excel工作表XML不完整');
        }finally{$r->close();libxml_clear_errors();libxml_use_internal_errors($old);}
    }
}
