evosoftcz / xml-string-streamer
以低内存消耗流式处理大型XML文件
Requires
- php: ^8.2
Requires (Dev)
- mockery/mockery: ^1.3.0
- phpunit/phpunit: ^9.5.0
- prewk/xml-faker: ^0.0.2
Replaces
This package is auto-updated.
Last update: 2024-09-09 19:29:17 UTC
README
目的
为了以非常低的内存消耗流式处理无法放入内存的XML文件。此库是XmlStreamer的后继者。
安装
当前PHP支持
- 所有8.0以上版本
使用composer
运行 composer require evosoftcz/xml-string-streamer
来安装此包。
用法
假设你有一个2GB的XML文件 gigantic.xml,其中包含如下外观的客户项目
<?xml version="1.0" encoding="UTF-8"?> <gigantic> <customer> <firstName>Jane</firstName> <lastName>Doe</lastName> </customer> ... </gigantic>
创建一个流器并解析它
// Convenience method for creating a file streamer with the default parser $streamer = Prewk\XmlStringStreamer::createStringWalkerParser("gigantic.xml"); while ($node = $streamer->getNode()) { // $node will be a string like this: "<customer><firstName>Jane</firstName><lastName>Doe</lastName></customer>" $simpleXmlNode = simplexml_load_string($node); echo (string)$simpleXmlNode->firstName; }
没有便利方法(功能等效)
use Prewk\XmlStringStreamer; use Prewk\XmlStringStreamer\Stream; use Prewk\XmlStringStreamer\Parser; // Prepare our stream to be read with a 1kb buffer $stream = new Stream\File("gigantic.xml", 1024); // Construct the default parser (StringWalker) $parser = new Parser\StringWalker(); // Create the streamer $streamer = new XmlStringStreamer($parser, $stream); // Iterate through the `<customer>` nodes while ($node = $streamer->getNode()) { // $node will be a string like this: "<customer><firstName>Jane</firstName><lastName>Doe</lastName></customer>" $simpleXmlNode = simplexml_load_string($node); echo (string)$simpleXmlNode->firstName; }
UniqueNode解析器的便利方法
$streamer = Prewk\XmlStringStreamer::createUniqueNodeParser("file.xml", array("uniqueNode" => "customer"));
解析器
Parser\StringWalker
类似于XmlReader,逐节点遍历XML树。通过设置节点深度进行捕获。
Parser\UniqueNode
一个捕获提供元素的开闭标签之间所有内容的更快的解析器。有特殊的先决条件。
流提供者
Stream\File
使用此提供者来解析磁盘上的大型XML文件。选择一个块大小,例如:1024字节。
$CHUNK_SIZE = 1024; $provider = new Prewk\XmlStringStreamer\Stream\File("large-xml-file.xml", $CHUNK_SIZE);
Stream\Stdin
如果你想创建一个通过STDIN流式处理大型XML文件的CLI应用程序,请使用此提供者。
$CHUNK_SIZE = 1024; $fsp = new Prewk\XmlStringStreamer\Stream\Stdin($CHUNK_SIZE);
Stream\Guzzle
如果你想使用Guzzle通过HTTP流式处理,请使用此提供者。由于其较高的PHP版本要求(5.5),位于其自己的仓库中:https://github.com/prewk/xml-string-streamer-guzzle
StringWalker选项
用法
use Prewk\XmlStringStreamer; use Prewk\XmlStringStreamer\Parser; use Prewk\XmlStringStreamer\Stream; $options = array( "captureDepth" => 3 ); $parser = new Parser\StringWalker($options);
StringWalker解析器的可用选项
示例
captureDepth
默认行为,捕获深度为2
<?xml encoding="utf-8"?> <root-node> <capture-me> ... </capture-me> <capture-me> ... </capture-me> </root-node>
..将捕获<capture-me>
节点。
但假设你的XML看起来像这样
<?xml encoding="utf-8"?> <root-node> <a-sub-node> <capture-me-instead> ... </capture-me-instead> <capture-me-instead> ... </capture-me-instead> </a-sub-node> </root-node>
那么你需要将捕获深度设置为3
以捕获<capture-me-instead>
节点。
节点深度可视化
<?xml?> <!-- Depth 0 because it's at the root and doesn't affect depth --> <root-node> <!-- Depth 1 because it's at the root and increases depth --> <a-sub-node> <!-- Depth 2 because it has one ancestor and increases depth --> <capture-me-instead> <!-- Depth 3 because it has two ancestors and increases depth --> </capture-me-instead> </a-sub-node> </root-node>
tags
默认值
array( array("<?", "?>", 0), array("<!--", "-->", 0), array("<![CDATA[", "]]>", 0), array("<!", ">", 0), array("</", ">", -1), array("<", "/>", 0), array("<", ">", 1) ),
第一个参数:开标签,第二个参数:闭标签,第三个参数:深度。
如果你知道你的XML没有任何XML注释、CDATA或自闭合标签,你可以通过设置tags选项并省略它们来调整性能
array( array("<?", "?>", 0), array("<!", ">", 0), array("</", ">", -1), array("<", ">", 1) ),
expectGT & tagsWithAllowedGT
如果你想允许在XML注释和CDATA部分中使用>
字符,你可以这样做。这很罕见,因此默认关闭以提高性能。
tagsWithAllowedGT的默认值
array( array("<!--", "-->"), array("<![CDATA[", "]]>") ),
UniqueNode选项
用法
use Prewk\XmlStringStreamer; use Prewk\XmlStringStreamer\Parser; use Prewk\XmlStringStreamer\Stream; $options = array( "uniqueNode" => "TheNodeToCapture" ); $parser = new Parser\UniqueNode($options);
UniqueNode解析器的可用选项
示例
uniqueNode
假设你有一个像这样的XML文件
<?xml encoding="utf-8"?> <root-node> <stuff foo="bar"> ... </stuff> <stuff foo="baz"> ... </stuff> <stuff foo="123"> ... </stuff> </root-node>
你想捕获内容节点,因此将uniqueNode设置为"stuff"
。
如果你有一个像这样的具有短闭标签的XML文件
<?xml encoding="utf-8"?> <root-node> <stuff foo="bar" /> <stuff foo="baz"> ... </stuff> <stuff foo="123" /> </root-node>
你想捕获内容节点,因此将uniqueNode设置为"stuff"
并将checkShortClosing设置为true
。
但如果你的XML文件看起来像这样
<?xml encoding="utf-8"?> <root-node> <stuff foo="bar"> <heading>Lorem ipsum</heading> <content> <stuff>Oops, another stuff node</stuff> </content> </stuff> ... </root-node>
..你将无法使用UniqueNode解析器,因为<stuff>
存在于另一个<stuff>
节点中。
高级用法
进度条
当构造流类时,你可以使用闭包作为第三个参数来跟踪进度。以下是一个使用File
流和StringWalker
解析器的示例
use Prewk\XmlStringStreamer; use Prewk\XmlStringStreamer\Stream\File; use Prewk\XmlStringStreamer\Parser\StringWalker; $file = "path/to/file.xml"; // Save the total file size $totalSize = filesize($file); // Construct the file stream $stream = new File($file, 16384, function($chunk, $readBytes) use ($totalSize) { // This closure will be called every time the streamer requests a new chunk of data from the XML file echo "Progress: $readBytes / $totalSize\n"; }); // Construct the parser $parser = new StringWalker; // Construct the streamer $streamer = new XmlStringStreamer($parser, $stream); // Start parsing while ($node = $streamer->getNode()) { // .... }
您当然可以做些比使用 echo
更智能的事情。
访问根元素(版本 0.7.0+)
设置解析器选项 extractContainer
告诉解析器收集您打算捕获子元素之前和之后的所有内容。结果可以通过解析器的 getExtractedContainer()
方法获取。
注意: getExtractedContainer()
的返回值取决于您是否已流式传输整个文件。如果您需要提前获取包含的XML数据,可以在循环内部获取它,但将只包含打开元素,因此它被视为由SimpleXML等解析器认为是无效的XML。
use Prewk\XmlStringStreamer; use Prewk\XmlStringStreamer\Stream\File; use Prewk\XmlStringStreamer\Parser\StringWalker; $file = "path/to/file.xml"; // Construct the file stream $stream = new File($file, 16384); // Construct the parser $parser = new StringWalker(array( "extractContainer" => true, // Required option )); // Construct the streamer $streamer = new XmlStringStreamer($parser, $stream); // Start parsing while ($node = $streamer->getNode()) { // .... } // Get the containing XML $containingXml = $parser->getExtractedContainer(); $xmlObj = simplexml_load_string($containingXml); $rootElementName = $xmlObj->getName(); $rootElementFooAttribute = $xmlObj->attributes()->foo;
该方法应被视为实验性的,并且在边缘情况下可能会提取奇怪的内容。