【问题标题】:Split XML into smaller files using DomDocument使用 DomDocument 将 XML 拆分为更小的文件
【发布时间】:2017-11-08 20:56:13
【问题描述】:

我正在尝试处理一个大的 (20mb+) XML 文件,但由于它的大小而无法解析它。我想把它分解成更小的部分,比如每个文件 100 个属性节点。

目前我正在使用下面的代码根据其他标准分离文件,但我有点不确定如何继续调整代码以执行每个文件拆分 100 条记录:

$destination = new DOMDocument;
$destination->preserveWhiteSpace = true;
$destination->loadXML('<?xml version="1.0" encoding="utf-8"?><root></root>');

$source = new DOMDocument;
$source->load('bes1c8ca168f910.xml');

$xp = new DOMXPath($source);
$destRoot = $destination->getElementsByTagName("root")->item(0);

foreach ($xp->query('/root/property[rent]') as $item) {
    $newItem = $destination->importNode($item, true);
    $destRoot->appendChild($newItem);
    $item->parentNode->removeChild($item);
}

$source->save("sales.xml");
$destination->formatOutput = true;
$destination->save("rentals.xml");

感谢任何建议。

【问题讨论】:

  • 如果您因为它的大小而在解析它时遇到问题,那么我不确定是否尝试再次拆分它使用解析器是解决方案。无论如何,如果你想尝试,那么它非常简单 - 在节点上的循环中保留一个计数器,当它达到 100 时,是时候关闭并保存当前文档,然后开始一个新文档...
  • 考虑使用 XMLReader 将整个文件解析为一个片段,而不是拆分它。看看像stackoverflow.com/questions/1835177/how-to-use-xmlreader-in-php 这样的东西,它展示了如何读取一个大文件,但仍然以块的形式处理它。
  • OP - 我调整了我的答案,现在 PHP 将参数传递给外部 XSLT 脚本以执行类似于 PHP 将参数绑定到 SQL 语句的拆分。 XSLT 和 SQL 作为专用的声明性语言具有相似之处。我知道 XSLT 是新的和强大的,但给它一个机会。

标签: php xml domdocument


【解决方案1】:

考虑动态XSLT,其中PHP 传递100 的倍数以使用XSLT 的position() 将源XML 中的节点范围解析为更小的输出。具体来说,PHP 将循环变量作为参数传递给绑定到$splitnum 的 XSLT(非常类似于 SQL 参数化)。

输入 (假设 XML 结构类似于您之前的帖子)

<root>
    <property>
      <rent>
        <term>short</term>
        <freq>week</freq>
        <price_peak>5845</price_peak>
        <price_high>5845</price_high>
        <price_medium>4270</price_medium>
        <price_low>3150</price_low>
      </rent>
    </property>
    <property>
      <rent>
        <term>long</term>
        <freq>week</freq>
        <price_peak>6845</price_peak>
        <price_high>6845</price_high>
        <price_medium>4270</price_medium>
        <price_low>3150</price_low>
      </rent>
    </property>
    ...
</root>

XSLT

(另存为.xsl文件,特殊的.xml文件;脚本需要传入参数进行节点范围分割)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
   <xsl:strip-space elements="*" />

   <xsl:param name="splitnum" />

   <xsl:template match="/root">
      <xsl:copy>
         <xsl:variable name="currsplit" select="$splitnum - 99"/>
         <xsl:apply-templates select="property[position() &gt;= $currsplit and 
                                               position() &lt;= $splitnum]" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="property">
      <xsl:copy>
         <xsl:copy-of select="*" />
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

PHP

(将循环迭代器变量作为参数传递给 XSLT;生成 10 个 XML,每个具有连续 100 个属性节点,根据需要扩展 1000 个限制)

// Load XML and XSL
$xml = new DOMDocument;
$xml->load('Input.xml');

$xsl = new DOMDocument;
$xsl->load($xslstr);

$prop_total = $xml->getElementsByTagName('property')->length + 100;

for($i=1; $i<=$prop_total; $i++){
  if ($i % 100 == 0) {         
    // Configure transformer
    $proc = new XSLTProcessor;
    $proc->importStyleSheet($xsl);

    // Binds loop variable to XSLT parameter
    $proc->setParameter('', 'splitnum', $i);

    // Transform XML source
    $newXML = new DOMDocument;
    $newXML = $proc->transformToXML($xml);

    // Output file
    file_put_contents('rentals_'.$i.'.xml', $newXML);
  }
}

【讨论】:

  • 我尝试了您的代码并仅调整了路径等。它拆分了文件,但每个文件仅运行 2 个属性节点。您发布的早期代码运行良好,并且完美地拆分了文件,但在更新和测试您的最新版本时我没有保留副本。
  • 哎呀!抱歉,只需在 XSLT 中更改:$splitnum - 1$splitnum - 99。见编辑。顺便说一句,我必须感谢你提出这个问题。我从未使用 PHP 将参数传递给 XSLT。如果不是你的,这肯定会进入我的图书馆!
  • 效果很好!我很高兴我们能互相帮助冻糕。我将更深入地研究 XSLT,因为它似乎对我将要做的事情非常方便:)。再次感谢。
【解决方案2】:

使用 XMLReader 拆分文件的示例。我试图让它变得灵活,所以文件名被用作创建文件的基础,分割计数被定义为一个变量。

代码的主要部分是一个读取&lt;property&gt; 元素的循环,您可以根据需要对其进行调整。我还使用$rootNodeName 作为你的根节点被调用的占位符。

$fileName = "data/t1.xml";
$original = new XMLReader;
$original->open($fileName);
$path_parts = pathinfo($fileName);
$filePrefix = $path_parts['dirname'].'/'.$path_parts['filename'].'-';
$nextRecord = 0;
$splitCount = 2;
$rootNodeName = "data";

$doc = new DOMDocument();
$doc->loadXML("<$rootNodeName/>");
while ($original->read() && $original->name !== 'property');
while ($original->name === 'property')
{
    $newNode = $doc->importNode($original->expand(), true);
    $doc->documentElement->appendChild($newNode);
    $nextRecord++;

    if ( $nextRecord % $splitCount == 0 )   {
        $nextFileName = $filePrefix.$nextRecord.".".$path_parts['extension'];
        $doc->save($nextFileName);
        $doc = new DOMDocument();
        $doc->loadXML("<$rootNodeName/>");
    }
    $original->next('property');
}
if ( $nextRecord % $splitCount != 0 )   {
    $nextFileName = $filePrefix.$nextRecord.".".$path_parts['extension'];
    $doc->save($nextFileName);
}

这不是最优雅的代码,但它也可以构成一个程序的基础,逐个处理元素,而不是一次加载整个文档。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    相关资源
    最近更新 更多