prewk/xml-string-streamer

以低内存消耗流式传输大型XML文件

1.2.3 2023-03-03 19:01 UTC

README

目的

为了以非常低的内存消耗流式传输太大而无法放入内存的XML文件。这个库是XmlStreamer的继承者。

安装

旧版支持

  • 所有低于1的版本都支持PHP 5.3 - 7.2
  • 1版本及以上支持PHP 7.2+

使用composer

运行composer require prewk/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设置为"content"

如果您有一个如下所示的具有短关闭标签的XML文件

<?xml encoding="utf-8"?>
<root-node>
    <stuff foo="bar" />
    <stuff foo="baz">
        ...
    </stuff>
    <stuff foo="123" />
</root-node>

您想捕获内容节点,因此将uniqueNode设置为"content"并将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解析器,因为<content>位于另一个<content>节点内部。

高级用法

进度条

您可以在构造流类时使用闭包作为第三个参数来跟踪进度。以下是一个使用StringWalker解析器和File流器的示例

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数据,可以在while循环内获取,但这仅包括开始元素,因此会被像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;

该方法应被视为实验性的,并且在边缘情况下可能会提取奇怪的内容。