evosoftcz/xml-string-streamer

以低内存消耗流式处理大型XML文件

1.2 2022-12-09 15:49 UTC

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;

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