files

object

%CSV

/phlo/resources/files/CSV.phlo

CSV reader resource

Reads the first line as the header and picks its own separator by counting: more commas than semicolons and it is a comma, otherwise a semicolon. Every row comes back keyed by header name, so a file with duplicate or empty headers loses columns. It reads, it does not write.

filecsvreaderimport
static

CSV :: __handle

line 10
CSV::$__handle 是对用于读取或写入位于指定路径和文件名的 CSV 数据的文件句柄的引用。
"CSV/$path$filename"
method

%CSV -> __construct (string $filename, ?string $path = null)

line 11
通过从给定的文件名和可选路径(默认为'data')构造文件路径来初始化CSV对象。如果构造的文件可读,则调用objRead方法以读取CSV数据。
$path ??= data
$this->objFile = $path.strtr($filename, [slash => dot]).'.csv'
if (is_readable($this->objFile)) $this->objRead()
readonly

%CSV -> objFile:string

line 17
将CSV文件转换为obj表示,以便在Phlo中更容易操作。
method

%CSV -> objRead:void

line 19
读取CSV文件并将其内容转换为对象的关联数组,使用第一行作为标题。
$fp = fopen($this->objFile, 'r+')
$headers = str_replace([dq, cr, lf], void, fgets($fp))
$delimiter = substr_count($headers, comma) > substr_count($headers, semi) ? comma : semi
$headers =  explode($delimiter, $headers)
while ($row = fgetcsv($fp, null, $delimiter, dq, void)) $this->objData[] = array_combine($headers, $row)
fclose($fp)
object

%DOCX

/phlo/resources/files/DOCX.phlo

DOCX reader resource

Pulls the plain text out of a .docx and nothing more: no styling, no tables as tables, no images. Paragraphs come out as a list, which is what makes it usable for search and for feeding a model, and DOCX::toText() is the one-liner for that.

filedocxwordreader
method

%DOCX -> __construct (string $file)

line 11
通过打开指定的DOCX文件,提取'word/document.xml'中的文本,并将其处理为段落来初始化DOCX对象。
$zip = new ZipArchive()
if ($zip->open($file) !== true) dx('error opening docx', $file)
$xml = $zip->getFromName('word/document.xml')
$zip->close()
if (!$xml) dx('error reading document.xml')
$text = preg_replace('/<\/w:p>/', lf, $xml)
$text = strip_tags($text)
$text = html_entity_decode($text, ENT_QUOTES | ENT_XML1, 'UTF-8')
$this->text = trim(preg_replace('/[ \t]+/', space, $text))
$this->paragraphs = array_values(array_filter(explode(lf, $this->text), fn($p) => trim($p) !== void))
static

DOCX :: toText (string $file):string

line 24
将DOCX文件的内容转换为纯文本格式。
(new static($file))->text
object

%file

/phlo/resources/files/file.phlo

File resource

Wrap a path in %file and everything about it is one call away: contents, size, mime, hashes, human dates, and a token() derived from the file's sha1, so the same content always yields the same token. Watch the difference between file and name: file is where it sits, name is what it is called, and ext and mime read the name. An upload therefore keeps its extension while the temp path has none.

filefilesystemio
static

file :: __handle

line 10
该表达式通过将'file/$file'与可选名称(如果提供)连接来构建文件路径。
"file/$file".($name ? "/$name" : void)
method

%file -> __construct (public string $file, ?string $name = null, $contents = null, ...$args)

line 11
使用指定的文件名、可选名称和可选内容初始化文件对象,同时允许额外的参数用于对象导入。
$name && $this->name = $name
is_string($contents) && $this->write($contents)
$args && $this->objImport(...$args)
method

%file -> append (string $data):int|false

line 17
将指定的字符串数据附加到文件末尾,并确保在操作期间的独占访问。
file_put_contents($this->file, $data, FILE_APPEND | LOCK_EX)
prop

%file -> basename:string

line 18
从给定的文件路径中返回文件的基本名称。
pathinfo($this->file, PATHINFO_BASENAME)
method

%file -> base64:string

line 19
将文件的内容编码为 Base64 字符串。
base64_encode($this->contents)
method

%file -> contents:string|false

line 20
将文件的全部内容读取为字符串。
file_get_contents($this->file)
method

%file -> contentsINI (bool $parse = true):array|false

line 21
读取文件的内容并将其解析为INI字符串,返回一个关联数组。$parse参数决定是否使用类型化或原始扫描INI数据。
parse_ini_string($this->contents, true, $parse ? INI_SCANNER_TYPED : INI_SCANNER_RAW)
method

%file -> contentsJSON ($assoc = null)

line 22
将存储在contents属性中的JSON字符串解码为PHP变量,如果$assoc参数设置为true,则可选择返回关联数组。
json_decode($this->contents, $assoc)
method

%file -> copy ($to):bool

line 23
将当前对象表示的文件复制到指定的目标路径。
copy($this->file, $to)
method

%file -> created:int|false

line 24
返回文件创建的最后时间的Unix时间戳。
filectime($this->file)
method

%file -> createdAge:int

line 25
根据文件的创建时间戳返回文件的年龄。
age($this->created)
method

%file -> createdHuman:string

line 26
返回文件创建时间的人类可读表示。
time_human($this->created)
method

%file -> curl ($type = null, $filename = null):CURLFile

line 27
为cURL请求创建一个新的CURLFile对象,允许指定文件类型和文件名。
new CURLFile($this->file, $type, $filename)
method

%file -> delete:bool

line 28
如果指定的文件存在,则删除该文件,并返回指示操作成功或失败的调试消息。
first($deleted = $this->exists && unlink($this->file), debug($deleted ? "Deleted $this->basename" : "Could not delete $this->basename"))
method

%file -> exists:bool

line 29
检查指定的文件在文件系统中是否存在。
file_exists($this->file)
prop

%file -> ext:string

line 30
使用PHP的pathinfo函数从存储在'name'属性中的文件名中检索文件扩展名。
pathinfo($this->name, PATHINFO_EXTENSION)
prop

%file -> filename:string

line 31
从存储在 'file' 属性中的文件路径中提取文件名。
pathinfo($this->file, PATHINFO_FILENAME)
method

%file -> getLine:string|false

line 32
从文件指针中获取一行,失败时返回 false,成功时返回去除尾部空白的行。
($line = fgets($this->pointer)) === false ? false : rtrim($line)
method

%file -> getLength (int $length):string|false

line 33
通过从文件指针读取指定数量的字节来获取文件的长度。
fread($this->pointer, $length)
method

%file -> is (string $file):bool

line 34
检查指定的文件是否与当前文件实例匹配。
$file === $this->file
method

%file -> md5:string|false

line 35
计算指定文件的MD5哈希值。
md5_file($this->file)
prop

%file -> mime:string

line 36
根据文件名返回文件的MIME类型。
mime($this->name)
method

%file -> modified:int|false

line 37
返回指定文件的最后修改时间,格式为 Unix 时间戳。
filemtime($this->file)
method

%file -> modifiedAge:int

line 38
根据文件最后修改的时间戳返回文件的年龄。
age($this->modified)
method

%file -> modifiedHuman:string

line 39
返回文件最后修改时间的可读表示。
time_human($this->modified)
method

%file -> move ($to):bool

line 40
将当前文件移动到由 $to 指定的新位置,并在成功时更新文件引用。
rename($this->file, $to) && $this->file = $to
prop

%file -> name:string

line 41
这将获取对象所表示文件的基本名称,即不带任何目录路径的文件名。
$this->basename
method

%file -> output ($download = false)

line 42
输出文件的内容,如果指定,则允许下载选项。
output($this->contents, $this->name, $download)
prop

%file -> path:string

line 43
这会获取指定文件的目录路径并在其后附加一个斜杠。
realpath(pathinfo($this->file, PATHINFO_DIRNAME)).slash
prop

%file -> pathRel:string

line 44
这个表达式通过检查文件路径是否以'app'开头来返回文件的相对路径,如果是,则去掉该前缀;否则返回原始文件路径。
str_starts_with($this->file, app) ? substr($this->file, strlen(app)) : $this->file
prop

%file -> pointer

line 45
以读写模式打开指定文件,返回指向文件资源的指针。
fopen($this->file, 'r+')
method

%file -> readable:bool

line 46
检查指定的文件是否可读。
is_readable($this->file)
method

%file -> src:string

line 47
该表达式生成一个文件的数据URI,结合其MIME类型和base64编码的内容。
"data:$this->mime;base64,$this->base64"
method

%file -> size:int|false

line 48
返回指定文件的大小(以字节为单位)。
filesize($this->file)
method

%file -> sizeHuman (int $precision = 0):string

line 49
将文件大小(以字节为单位)转换为人类可读的格式,并可选择小数位数的精度。
size_human($this->size, $precision)
method

%file -> sha1:string|false

line 50
计算指定文件的SHA-1哈希值。
sha1_file($this->file)
method

%file -> shortenTo (int $length):string

line 51
将文件名缩短到指定长度,同时保留文件扩展名,如果名称被截断,则添加省略号。
strlen($this->name) <= $length ? $this->name : substr($this->name, 0, $length - strlen($this->ext) - 3).dot.dot.dot.$this->ext
method

%file -> title:string

line 52
从文件名中提取标题,通过将其转换为可读格式,替换下划线为空格并将首字母大写。
ucfirst(strtr(pathinfo($this->name, PATHINFO_FILENAME), [us => space]))
method

%file -> token ($length = 20):string

line 53
使用SHA-1哈希生成指定长度的令牌。
token($length, $this->sha1)
method

%file -> type:string

line 54
通过提取第一个斜杠之前的子字符串,从 MIME 类型中提取文件类型。
substr($this->mime, 0, strpos($this->mime, slash))
method

%file -> touch:bool

line 55
创建一个新文件或更新由文件路径指定的现有文件的时间戳。
touch($this->file)
method

%file -> writable:bool

line 56
检查指定的文件是否可写。
is_writable($this->file)
method

%file -> writeINI ($data, bool $deleteEmpty = false):bool

line 57
将数据写入INI文件格式。可选参数允许在设置为true时删除空节。
$this->write(!$deleteEmpty || $data ? loop($data, fn($value, $key) => $key.' = '.dq.strtr($value, [dq => bs.dq, lf => '\n']).dq, lf).lf : void, $deleteEmpty)
method

%file -> writeJSON ($data, bool $deleteEmpty = false):bool

line 58
$this->write(!$deleteEmpty || $data ? json_encode($data, jsonPretty) : void, $deleteEmpty)
method

%file -> writeJSONplain ($data, bool $deleteEmpty = false):bool

line 59
将提供的数据作为 JSON 字符串写入文件,基于 deleteEmpty 标志可选择性地删除空条目。
$this->write(!$deleteEmpty || $data ? json_encode($data) : void, $deleteEmpty)
method

%file -> write (string $data, bool $deleteEmpty = false):bool

line 60
将指定的字符串数据写入文件,并可以选择在数据为空时删除该文件。
if (!$data && $deleteEmpty) return $this->delete
if ($written = file_put_contents($this->file, $data, LOCK_EX) !== false) debug('Written '.$this->basename.' ('.$this->sizeHuman.')')
else error('Could not write '.$this->file)
return $written
method

%file -> objInfo:array

line 67
创建一个包含文件信息的对象,包括其名称、存在性,以及如果存在则可选的大小、创建日期、修改日期和MIME类型。
array_combine($keys = array_merge(['file', 'name', 'exists'], $this->exists ? ['sizeHuman', 'createdHuman', 'modifiedHuman', 'mime'] : []), loop($keys, fn($arg) => $this->$arg))
object

%img

/phlo/resources/files/img.phlo

GD image resource

Only scales down, never up: a request larger than the original returns the image untouched, so a thumbnail never looks stretched. The output format follows the extension you save to, so saving a .jpg writes JPEG at quality 85 whatever came in. Pass crop with a width and a height to fill the frame instead of fitting inside it, and top or bottom to choose which part survives.

imagegdfilegraphics
static

img :: detect ($data):?string

line 10
通过检查给定二进制数据的头部来检测图像格式,并返回相应的格式字符串。
$header = substr($data, 0, 12)
if (substr($header, 0, 3) === "\xFF\xD8\xFF") return 'jpg'
if (substr($header, 0, 8) === "\x89PNG\x0D\x0A\x1A\x0A") return 'png'
if (substr($header, 0, 6) === 'GIF87a' || substr($header, 0, 6) === 'GIF89a') return 'gif'
if (substr($header, 0, 4) === 'RIFF' && substr($header, 8, 4) === 'WEBP') return 'webp'
if (substr($header, 0, 2) === "BM") return 'bmp'
if (substr($header, 0, 4) === "\x49\x49\x2A\x00" || substr($header, 0, 4) === "\x4D\x4D\x00\x2A") return 'tiff'
static

img :: __handle

line 20
这从指定的文件路径中检索图像资源。
"img/$file"
method

%img -> __construct (public string $file)

line 21
使用公共字符串属性$file初始化img对象,该属性表示图像的文件路径。
prop

%img -> src:GdImage

line 23
此函数从包含图像数据的字符串创建图像资源,通常是从文件加载的。
imagecreatefromstring(file_get_contents($this->file))
prop

%img -> width:int

line 24
获取由`src`属性指定的图像资源的宽度。
imagesx($this->src)
prop

%img -> height:int

line 25
返回由`src`属性指定的图像的高度。
imagesy($this->src)
method

%img -> scale ($width = null, $height = null, $crop = false):static

line 27
将图像缩放到指定的宽度和高度,基于提供的参数可选择性地裁剪。
if (!$width && !$height) return $this
$srcW = $this->width
$srcH = $this->height
$doCrop = ($crop && $width && $height)
if ($width && $height) $scale = $doCrop ? max($width / $srcW, $height / $srcH) : min($width / $srcW, $height / $srcH)
elseif ($width) $scale = $width / $srcW
else $scale = $height / $srcH
if ($scale >= 1) return $this
$scaledW = (int)round($srcW * $scale)
$scaledH = (int)round($srcH * $scale)
$destW = ($width && $height && $doCrop) ? (int)$width : $scaledW
$destH = ($width && $height && $doCrop) ? (int)$height : $scaledH
$offsetX = 0
$offsetY = 0
if ($width && $height && $doCrop){
	$offsetX = (int)-round(($scaledW - $destW) / 2)
	$offsetY = (int)-round(($scaledH - $destH) / 2)
	if ($crop === 'top') $offsetY = 0
	elseif ($crop === 'bottom') $offsetY = (int)-($scaledH - $destH)
}
$destImg = imagecreatetruecolor($destW, $destH)
imagealphablending($destImg, false)
imagesavealpha($destImg, true)
imagecopyresampled($destImg, $this->src, $offsetX, $offsetY, 0, 0, $scaledW, $scaledH, $srcW, $srcH)
$this->src = $destImg
$this->width = $destW
$this->height = $destH
return $this
method

%img -> ext ($file = null):string

line 58
返回指定文件的小写文件扩展名。如果未提供文件,则使用实例的默认文件。
strtolower(pathinfo($file ?? $this->file, PATHINFO_EXTENSION))
method

%img -> source ($format = null):string

line 60
以指定格式检索图像的源,如果未提供,则使用默认格式,通过捕获write方法的输出。
ob_start()
$this->write($format)
return ob_get_clean()
method

%img -> save ($file = null):bool

line 66
如果提供了文件路径,则将图像保存到指定的文件路径;否则,将图像写入默认位置。
$file && $this->file = $file
return $this->write(null, $this->file)
method

%img -> write ($format = null, $file = null)

line 71
将图像以指定格式写入文件,如果未提供格式,则默认为JPEG。支持的格式包括PNG、GIF、WebP和JPEG。
$format ??= $this->ext()
if ($format === 'png') return imagepng($this->src, $file, 8)
if ($format === 'gif') return imagegif($this->src, $file)
if ($format === 'webp'){
	imageistruecolor($this->src) || imagepalettetotruecolor($this->src)
	return imagewebp($this->src, $file)
}
return imagejpeg($this->src, $file, 85)
object

%INI

/phlo/resources/files/INI.phlo

Generic INI resource

The same shape as the JSON resource, saved when the object goes out of scope, but writing flattens the file: comments and section headers do not survive a round trip. Keep it to values a program owns; a file a person edits by hand deserves to be read rather than rewritten.

fileiniconfigparser
prop

%INI -> objFile:string

line 10
INI->$objFile 用于创建或访问 INI 文件对象,以读取或写入配置设置。
static

INI :: __handle

line 12
INI::$__handle 获取指定 INI 文件的句柄,可根据提供的参数选择性解析。
"INI/$path$filename".(!$parse ? '/0' : void)
method

%INI -> __construct (string $filename, ?string $path = null, bool $parse = true)

line 13
使用指定的文件名和可选路径初始化一个INI对象,并在文件可访问时读取该文件。
$path ??= data
$this->objFile = $path.strtr($filename, [slash => dot]).'.ini'
if (is_readable($this->objFile)) $this->objRead($parse)
method

%INI -> objRead ($parse = true)

line 19
读取一个 INI 文件并将其转换为 obj,值可以选择性地解析为各自的类型。
last($this->objData = parse_ini_file($this->objFile, true, $parse ? INI_SCANNER_TYPED : INI_SCANNER_RAW), $this->objChanged = false, $this)
method

%INI -> objWrite:int|false

line 20
将当前对象数据以INI文件格式写入,确保特殊字符被正确转义,并在写入操作期间锁定文件。
file_put_contents($this->objFile, loop($this->objData, fn($value, $key) => $key.' = '.dq.strtr($value, [dq => bs.dq, lf => '\n']).dq, lf).lf, LOCK_EX)
method

%INI -> __destruct

line 22
INI->__destruct 方法检查对象是否已更改,并在必要时调用 objWrite 方法以处理对象销毁前的清理工作。
$this->objChanged && $this->objWrite()
object

%JSON

/phlo/resources/files/JSON.phlo

Generic JSON resource

The file behaves as an object: read a key, write a key, and the file is saved when the object goes out of scope. That last part is the trap: nothing is written until then, so call objWrite() yourself when the request may end otherwise. Names are read against data/ and a slash in the name becomes a dot, so no name can escape the directory.

filejsonstorageparser
static

JSON :: __handle

line 11
此函数根据提供的文件名和一个关联布尔标志构建JSON文件路径,决定路径的格式。
"JSON/$path$filename".(is_bool($assoc) ? slash.(int)$assoc : void)
method

%JSON -> __construct (string $filename, ?string $path = null, $assoc = null)

line 12
根据指定的文件名和可选路径构造一个JSON对象,创建完整的文件路径,并在文件可访问时读取JSON数据。
$path ??= data
$this->objFile = $path.strtr($filename, [slash => dot]).'.json'
if (is_readable($this->objFile)) $this->objRead($assoc)
readonly

%JSON -> objFile:string

line 18
将 JSON 字符串转换为 Phlo obj 以便进一步操作或处理。
method

%JSON -> objTouch:bool

line 20
将 objChanged 属性设置为 true,表示对象已被修改。
$this->objChanged = true
method

%JSON -> objRead ($assoc = null)

line 21
读取 JSON 文件并根据提供的参数将其转换为对象或关联数组。
last($data = json_read($this->objFile, $assoc), $this->objData = $assoc || is_array($data) ? $data : get_object_vars($data), $this->objChanged = false, $this)
method

%JSON -> objWrite ($data, $flags = null)

line 22
将JSON数据写入对象文件, optionally使用指定的标志来修改写入行为。
first($written = json_write($this->objFile, $data, $flags), $written && $this->objChanged = false)
method

%JSON -> __destruct

line 24
当对象被销毁时调用此方法,确保在对象从内存中移除之前,任何对对象数据的更改都被写入。
$this->objChanged && $this->objWrite($this->objData)
object

%PDF

/phlo/resources/files/PDF.phlo

PDF generator and reader

Two halves that share a name. Reading uses the pdftotext binary, so it needs poppler-utils on the machine and gives nothing on a scan without a text layer. Writing renders HTML through mPDF, and mode is the mPDF one: D sends a download, I shows it in the browser, S returns the bytes as a string, so leaving the default on an API route pushes a download at your caller.

filepdfreadergenerator
static

PDF :: toText (string $file):string

line 10
使用 `pdftotext` 命令行工具将 PDF 文件转换为纯文本,并返回提取的文本。
$process = proc_open('pdftotext '.escapeshellarg($file).' -', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes)
if (!is_resource($process)) return null
fclose($pipes[0])
$text = stream_get_contents($pipes[1])
fclose($pipes[1])
$error = stream_get_contents($pipes[2])
fclose($pipes[2])
($code = proc_close($process)) && error("PDFToText Error: pdftotext command failed with code $code. Error: $error")
return rtrim($text, "\f")
prop

%PDF -> title:?string

line 22
生成具有指定标题的PDF文档。
null
prop

%PDF -> author:?string

line 23
检索PDF文档的作者。
null
prop

%PDF -> subject:?string

line 24
根据指定主题生成PDF文档。
null
prop

%PDF -> keywords:?string

line 25
生成从PDF文档中提取的关键词列表。
null
prop

%PDF -> creator:string

line 26
PDF->$creator 属性用于获取 PDF 文档的创建者信息。
'Phlo '.phlo.' (https://phlo.tech/)'
prop

%PDF -> filename:string

line 28
该项指定PDF资源的文件名为'Download.pdf'。
'Download.pdf'
prop

%PDF -> mode:string

line 29
设置PDF处理的模式,其中'D'表示特定的操作模式。
'D'
method

%PDF -> fromHTML ($HTML):string

line 31
使用Mpdf库从提供的HTML内容生成PDF文档,在输出文件之前设置标题、作者、主题、关键字和创建者等各种元数据属性。
$mpdf = new \Mpdf\Mpdf
$this->title && $mpdf->SetTitle($this->title)
$this->author && $mpdf->SetAuthor($this->author)
$this->subject && $mpdf->SetSubject($this->subject)
$this->keywords && $mpdf->SetKeywords($this->keywords)
$this->creator && $mpdf->SetCreator($this->creator)
$mpdf->WriteHTML($HTML)
return $mpdf->Output($this->filename, $this->mode)
object

%UBL

/phlo/resources/files/UBL.phlo

UBL 2.1 invoice XML (PEPPOL BIS Billing 3.0) from a normalized invoice structure. Use UBL::invoice($data).

Builds the invoice XML that PEPPOL expects from a plain array with supplier, customer and lines. Tax is grouped by rate and each group gets its own subtotal, so lines at 21 and 9 percent land in the right boxes on their own. It formats and escapes the amounts you hand it but checks nothing: a total that does not match its lines is written out as given, and a receiver will reject it.

ublpeppolinvoicexmle-invoicingexport
static

UBL :: invoice (array $data):string

line 10
根据提供的发票数据生成UBL发票XML结构,包括供应商和客户信息、行项目、税务计算和总金额。
$currency = strtoupper((string)($data['currency'] ?? 'EUR'))
$issue = (string)($data['issue_date'] ?? void)
$due = (string)($data['due_date'] ?? void) ?: $issue
$lines = []
$linesSum = 0
$taxSubtotals = []
foreach ((array)($data['lines'] ?? []) AS $i => $line){
	$qty = (float)($line['quantity'] ?? 0)
	$net = round($qty * (float)($line['unit_price'] ?? 0), 2)
	$rate = (float)($line['tax_rate'] ?? 0)
	$lines[] = static::xmlLine($i + 1, $line, $qty, $net, $rate, $currency)
	$linesSum += $net
	$key = number_format($rate, 2, dot, void)
	$taxSubtotals[$key] = ($taxSubtotals[$key] ?? 0) + $net
}
$tax = (float)($data['tax_amount'] ?? 0)
$total = (float)($data['total_amount'] ?? 0)
$taxXml = void
foreach ($taxSubtotals AS $rate => $taxable){
	$amount = round($taxable * (float)$rate / 100, 2)
	$category = (float)$rate === 0.0 ? 'Z' : 'S'
	$taxXml .= implode(void, [
		'<cac:TaxSubtotal>',
		'<cbc:TaxableAmount currencyID="'.$currency.'">'.static::n($taxable).'</cbc:TaxableAmount>',
		'<cbc:TaxAmount currencyID="'.$currency.'">'.static::n($amount).'</cbc:TaxAmount>',
		'<cac:TaxCategory><cbc:ID>'.$category.'</cbc:ID><cbc:Percent>'.static::n((float)$rate).'</cbc:Percent>',
		'<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:TaxCategory>',
		'</cac:TaxSubtotal>',
	])
}
$supplier = (array)($data['supplier'] ?? [])
$customer = (array)($data['customer'] ?? [])
return implode(void, [
	'<?xml version="1.0" encoding="UTF-8"?>',
	'<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">',
	'<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>',
	'<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>',
	'<cbc:ID>'.static::esc((string)($data['number'] ?? void)).'</cbc:ID>',
	'<cbc:IssueDate>'.static::esc($issue).'</cbc:IssueDate>',
	'<cbc:DueDate>'.static::esc($due).'</cbc:DueDate>',
	'<cbc:InvoiceTypeCode>'.(!empty($data['credit_note']) ? '381' : '380').'</cbc:InvoiceTypeCode>',
	'<cbc:DocumentCurrencyCode>'.$currency.'</cbc:DocumentCurrencyCode>',
	static::partyXml('AccountingSupplierParty', (string)($supplier['name'] ?? void), $supplier, (string)($supplier['vat'] ?? void)),
	static::partyXml('AccountingCustomerParty', (string)($customer['name'] ?? void), $customer, (string)($customer['vat'] ?? void)),
	'<cac:TaxTotal><cbc:TaxAmount currencyID="'.$currency.'">'.static::n($tax).'</cbc:TaxAmount>'.$taxXml.'</cac:TaxTotal>',
	'<cac:LegalMonetaryTotal>',
	'<cbc:LineExtensionAmount currencyID="'.$currency.'">'.static::n($linesSum).'</cbc:LineExtensionAmount>',
	'<cbc:TaxExclusiveAmount currencyID="'.$currency.'">'.static::n($linesSum).'</cbc:TaxExclusiveAmount>',
	'<cbc:TaxInclusiveAmount currencyID="'.$currency.'">'.static::n($total).'</cbc:TaxInclusiveAmount>',
	'<cbc:PayableAmount currencyID="'.$currency.'">'.static::n($total).'</cbc:PayableAmount>',
	'</cac:LegalMonetaryTotal>',
	implode(void, $lines),
	'</Invoice>',
])
static

UBL :: xmlLine ($idx, $line, $qty, $net, $rate, $currency = 'EUR'):string

line 67
生成发票行项目的XML表示,包括数量、金额、项目描述、税种和价格等详细信息。
$category = (float)$rate === 0.0 ? 'Z' : 'S'
return implode(void, [
	'<cac:InvoiceLine>',
	'<cbc:ID>'.$idx.'</cbc:ID>',
	'<cbc:InvoicedQuantity unitCode="EA">'.static::n($qty).'</cbc:InvoicedQuantity>',
	'<cbc:LineExtensionAmount currencyID="'.$currency.'">'.static::n($net).'</cbc:LineExtensionAmount>',
	'<cac:Item><cbc:Name>'.static::esc((string)($line['description'] ?? void)).'</cbc:Name>',
	'<cac:ClassifiedTaxCategory><cbc:ID>'.$category.'</cbc:ID><cbc:Percent>'.static::n((float)$rate).'</cbc:Percent>',
	'<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:ClassifiedTaxCategory></cac:Item>',
	'<cac:Price><cbc:PriceAmount currencyID="'.$currency.'">'.static::n((float)($line['unit_price'] ?? 0)).'</cbc:PriceAmount></cac:Price>',
	'</cac:InvoiceLine>',
])
static

UBL :: partyXml ($wrapper, $name, $info, $vatNumber):string

line 82
生成UBL的一个方元素的XML表示,包括名称、地址、城市、邮政编码、国家和税务信息等细节。
$address = (string)($info['address'] ?? void)
$postal = (string)($info['postal_code'] ?? void)
$city = (string)($info['city'] ?? void)
$country = (string)($info['country'] ?? void) ?: 'NL'
$tax = $vatNumber ? '<cac:PartyTaxScheme><cbc:CompanyID>'.static::esc($vatNumber).'</cbc:CompanyID><cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:PartyTaxScheme>' : void
return implode(void, [
	'<cac:'.$wrapper.'><cac:Party>',
	'<cac:PartyName><cbc:Name>'.static::esc($name).'</cbc:Name></cac:PartyName>',
	'<cac:PostalAddress>',
	'<cbc:StreetName>'.static::esc($address).'</cbc:StreetName>',
	'<cbc:CityName>'.static::esc($city).'</cbc:CityName>',
	'<cbc:PostalZone>'.static::esc($postal).'</cbc:PostalZone>',
	'<cac:Country><cbc:IdentificationCode>'.static::esc(strtoupper($country)).'</cbc:IdentificationCode></cac:Country>',
	'</cac:PostalAddress>',
	$tax,
	'<cac:PartyLegalEntity><cbc:RegistrationName>'.static::esc($name).'</cbc:RegistrationName></cac:PartyLegalEntity>',
	'</cac:Party></cac:'.$wrapper.'>',
])
static

UBL :: n ($v):string

line 103
使用指定的十进制点和千位分隔符将数字格式化为两位小数。
number_format((float)$v, 2, dot, void)
static

UBL :: esc ($v):string

line 104
将特殊字符转换为HTML实体以用于XML输出,确保UTF-8字符串的正确编码。
htmlspecialchars((string)$v, ENT_XML1 | ENT_QUOTES, 'UTF-8')
object

%XLSX

/phlo/resources/files/XLSX.phlo

XLSX reader resource

Reads a workbook without a library by unpacking the zip itself, and gives every sheet by name with the first row as the header. Values arrive as the sheet stored them, so a date is the serial number Excel keeps and a percentage is a fraction; convert those yourself. Formulas give the last calculated value, so a sheet that was never opened after an edit hands you the old one.

filexlsxexcelreader
method

%XLSX -> __construct (string $file)

line 16
Reads the sheets straight out of the zip with regular expressions instead of an XML parser.
$sheets = []
$shared = []
$sheetNames = []
$zip = new ZipArchive()
if ($zip->open($file) !== true) dx('error opening zip', $file)
for ($i = 0; $i < $zip->numFiles; $i++){
	$name = $zip->getNameIndex($i)
	if ($name === false) continue
	if (dirname($name) === 'xl/worksheets') $sheets[filter_var($name, FILTER_SANITIZE_NUMBER_INT)] = $zip->getFromIndex($i)
	elseif ($name === 'xl/sharedStrings.xml'){
		$xml = $zip->getFromIndex($i)
		if (!preg_match_all('/<t[^>]*>(.*?)<\/t>/s', $xml, $m)) dx('error reading shared lib')
		$shared = array_map(fn($t) => html_entity_decode($t, ENT_QUOTES | ENT_XML1, 'UTF-8'), $m[1])
	}
	elseif ($name === 'xl/workbook.xml'){
		$xml = $zip->getFromIndex($i)
		if (!preg_match_all('/<sheet[^>]*name="([^"]+)"[^>]*sheetId="([0-9]+)"/', $xml, $m)) dx('error reading workbook')
		$sheetNames = $m[1]
	}
}
$zip->close()
$toIndex = fn($letters) => array_reduce(str_split(strtoupper($letters)), fn($n, $c) => $n * 26 + ord($c) - 64, 0) - 1
$isShared = fn($attrs) => preg_match('/\bt="s"\b/', $attrs) === 1
foreach ($sheets AS $sheetID => $sheet){
	$name = $sheetNames[$sheetID - 1] ?? 'Sheet '.$sheetID
	if (!preg_match('/<row[^>]*>(.+)<\/row>/s', $sheet, $m)) dx('error parsing sheet')
	$rowsXml = preg_split('/<\/row><row[^>]*>/', $m[1]) ?: []
	$headerMap = []
	$isHeader = true
	foreach ($rowsXml AS $rowXml){
		$rowXml = preg_replace('/<c([^>]*)\/>/', '<c$1></c>', $rowXml)
		if (!preg_match_all('/<c r="([A-Z]+)[0-9]+"([^>]*)>(?:<f\b[^>]*\/?>)?(?:(?:<v>([^<]*)<\/v>)|(?:<is>.*?<t[^>]*>(.*?)<\/t>.*?<\/is>))?<\/c>/s', $rowXml, $mm)) dx('error parsing row', $rowXml)
		if ($isHeader){
			foreach (array_keys($mm[0]) AS $i){
				$col = $toIndex($mm[1][$i])
				$attrs = $mm[2][$i]
				$valV = $mm[3][$i] ?? null
				$valIS = $mm[4][$i] ?? null
				$val = $valV !== null && $valV !== void ? $valV : ($valIS !== null && $valIS !== void ? html_entity_decode($valIS, ENT_QUOTES | ENT_XML1, 'UTF-8') : null)
				$txt = $isShared($attrs) ? ($shared[$val] ?? null) : $val
				$headerMap[$col] = $txt !== null && $txt !== void ? $txt : 'col'.$col
			}
			$isHeader = false
		}
		else {
			$rowArr = []
			foreach (array_keys($mm[0]) AS $i){
				$col = $toIndex($mm[1][$i])
				$attrs = $mm[2][$i]
				$valV = $mm[3][$i] ?? null
				$valIS = $mm[4][$i] ?? null
				$val = $valV !== null && $valV !== void ? $valV : ($valIS !== null && $valIS !== void ? html_entity_decode($valIS, ENT_QUOTES | ENT_XML1, 'UTF-8') : null)
				$key = $headerMap[$col] ?? 'col'.$col
				$rowArr[$key] = $isShared($attrs) ? ($shared[$val] ?? null) : $val
			}
			$this->objData[$name][] = $rowArr
		}
	}
}

最近更新于 2026年8月8日

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。