如何通过简单快速的比较来检查DomDocument是否已更改?

时间:2014-07-05 12:51:22

标签: php domdocument

我只需要在某些操作之前和之后比较相同的DomDocument对象,如果操作改变了对象,则“快速检查”...我需要一些serialize_DomDocument()存在某事喜欢?)可以在以下环境中使用:

  1. $objDomDocumemt对象(“对象的状态”是所有属性和所有documentElement内容的值)。

  2. $dump = serialize_DomDocument($obj) 怎么做?如何转储对象的状态? ...不是通过saveXML()方法将所有内容转换为XML,而只是(更快!)将所有二进制表示($obj指向的对象)转换为$dump

    < / LI>
  3. 执行某些操作(例如删除节点或更改属性或“不执行任何操作”)

  4. if ($dump != serialize_DomDocument($obj))或类似内容,检查$obj是否已更改。快速比较。 操作是“什么都不做”或“做某事”?

  5. 替代解决方案......

    不理想,但解决了一些情况......有一些操作(例如只有appendChild或仅removeChild),其中更改总是影响节点数,所以,对于那种操作,检查节点总数总长度就足够了。

    如何检查速度超过saveXML()


    注意

    NOTE-1 :由this post记住,您无法序列化DomDocument对象。

    NOTE-2 :不是comparing two distinct DOM objects的问题,但更简单,因为不更改ID等不需要规范表示(!),只能访问内部表示


    BOUNTY后编辑

    这个问题不是关于“控制如何改变或改变标志”,请仔细阅读问题。

    这个问题不是理论评论的要求

    EDIT2

    也许解决方案是关于“低级别”...我不明白“二进制表示”是“转储”还是有其他名称,请参阅:

4 个答案:

答案 0 :(得分:1)

这是&#34;第二课&#34;解决方案,hoping "first class" here

...在没有直接解决方案的情况下,正如我在问题的文本中表达的那样,有&#34; 替代解决方案&#34; ...我今天在申请中做了什么:

使用XPath count(//node())我们可以检查是否更改,比saveXML() 更快,但是如果DOM具有相同数量的节点(例如。更改属性值),我们需要通过saveXML检查,花费内存和/或CPU时间来生成和比较XML。

// $old_dom is the original document, 
$old_xp = new DOMXpath($old_dom);
// ... holding $old_n (and perhaps $old_xml) in a cache 
$old_n = $old_xp->evaluate('count(//node())');
$old_xml = $old_dom->saveXML();
...

// $new_dom is the modified document, must tested at each "black box" change.
$new_xp = new DOMXpath($new_dom);
$new_n = $new_xp->evaluate('count(//node())');
if ($new_n!=$old_n) {  //  OK, fast!!
  //.. OK! do something because changed!
} // else, check with more detail if changed,
elseif ($new_dom->saveXML()!=$old_xml){ // memory and CPU-time consuming here!
  //.. OK! do something because changed!
}

正如@ArtisiticPhoenix建议的那样,让我们​​进行比较!

this simple gistreal life XML-documents of PubMed Central

基准

  $xp = new DOMXpath($dom);      
  $test = ( $xp->evaluate('count(//node())') == 123 );
    // Execution total time of 10000 loops: 0.582 seconds
    // Averaged execution time of each loop: 5.8152985572815E-5 seconds

  $xp = new DOMXpath($dom);      
  $test = ( $xp->evaluate('count(//*)') == 123 );
    // Execution total time of 10000 loops: 0.462 seconds
    // Averaged execution time of each loop: 4.6241903305054E-5 seconds
与其他解决方案比较时,

好10倍,

  $test = ( md5( $dom->saveXML() ) == 'ff1afedc7127eb221050fa48eee5153a');
    // Execution total time of 10000 loops: 3.877 seconds
    // Averaged execution time of each loop: 0.00038769061565399 seconds

  $test = ( $dom->saveXML() == $oldXml ); // comparing long strings 
    // Execution total time of 10000 loops: 3.168 seconds
    // Averaged execution time of each loop: 0.00031677310466766 seconds

  $test = ( $dom->C14N() == $oldXml ); // comparing long strings 
    // Execution total time of 10000 loops: 8.874 seconds
    // Averaged execution time of each loop: 0.00088736770153046 seconds

注意:直接 longString比较比md5 的 shortString更快。确认,

  $test = ( $dom->saveXML() == 'xxx' );
    //Execution total time of 10000 loops: 3.14 seconds
    //Averaged execution time of each loop: 0.00031396999359131 seconds

长/短的变化,即使在最坏的情况下(比较相同的字符串),也不会改变&#34; 0.31微秒&#34;直接表现,优于短期+ MD5&#34; 0.39微秒&#34;。


我在这种&#34;替代解决方案中寻找的东西&#34; ...

...是一个 DomDocument 内部属性,它返回&#34; DOM长度&#34;或&#34; DOM节点数&#34;,无需XPath计数程序。

PS:&#34;替代解决方案&#34;是为了这种答案。

答案 1 :(得分:1)

首先我没有使用过DOMDocument,但我会试一试(请阅读完整帖子)。

您可以使用C14N()方法,但没有详细记录,但是从我的IDE中我得到了这个:

/**
 * Canonicalize nodes to a string
 * @link http://www.php.net/manual/en/domnode.c14n.php
 * @param exclusive bool[optional] <p>
 * Enable exclusive parsing of only the nodes matched by the provided
 * xpath or namespace prefixes.
 * </p>
 * @param with_comments bool[optional] <p>
 * Retain comments in output.
 * </p>
 * @param xpath array[optional] <p>
 * An array of xpaths to filter the nodes by.
 * </p>
 * @param ns_prefixes array[optional] <p>
 * An array of namespace prefixes to filter the nodes by.
 * </p>
 * @return string canonicalized nodes as a string&return.falseforfailure;
*/
public function C14N ($exclusive = null, $with_comments = null, array $xpath = null, array $ns_prefixes = null) {}

我将简单地从这篇文章的PHP文档页面中获取DOMDocument的示例。

因此,对于我的例子,我有这个obj开始。 (注意我在评论中放置for循环的地方,我将使用后者进行基准测试):

    $xml = new \DOMDocument( "1.0", "ISO-8859-15" );
    $xml_album = $xml->createElement( "Album" );
    //---- for( $i=0; $i < 10000; $i++ ){  //for benchmarks I will be adding 30,000 nodes, to get something worth measuring performance on.
    // Create some elements.
    $xml_track = $xml->createElement( "Track", "The ninth symphony" );

    // Set the attributes.
    $xml_track->setAttribute( "length", "0:01:15" );
    $xml_track->setAttribute( "bitrate", "64kb/s" );
    $xml_track->setAttribute( "channels", "2" );

    // Create another element, just to show you can add any (realistic to computer) number of sublevels.
    $xml_note = $xml->createElement( "Note", "The last symphony composed by Ludwig van Beethoven." );

    // Append the whole bunch.
    $xml_track->appendChild( $xml_note );
    $xml_album->appendChild( $xml_track );

    // Repeat the above with some different values..
    $xml_track = $xml->createElement( "Track", "Highway Blues" );

    $xml_track->setAttribute( "length", "0:01:33" );
    $xml_track->setAttribute( "bitrate", "64kb/s" );
    $xml_track->setAttribute( "channels", "2" );
    $xml_album->appendChild( $xml_track );

    $xml->appendChild( $xml_album );
    //----- } //end for loop
    // Parse the XML.
    print $xml->saveXML();

或者粗略地说我们使用htmlspecialchars进行编码,还有一些标签:

<?xml version="1.0" encoding="ISO-8859-15"?>
<Album>
    <Track length="0:01:15" bitrate="64kb/s" channels="2">The ninth symphony
        <Note>The last symphony composed by Ludwig van Beethoven.</Note>
    </Track>
    <Track length="0:01:33" bitrate="64kb/s" channels="2">Highway Blues</Track>
</Album>

到目前为止好用的(记录不好的C14N())给了我们这个(减去好的缩进等等),注意它们几乎相同但顺序不同而且我们减去了编码位,所以我们我们不想将它们相互比较:

<Album>
    <Track bitrate="64kb/s" channels="2" length="0:01:15">The ninth symphony
        <Note>The last symphony composed by Ludwig van Beethoven.</Note>
    </Track>
    <Track bitrate="64kb/s" channels="2" length="0:01:33">Highway Blues</Track>
</Album>

现在通常这看起来与saveXML类似,但是它有一些用于过滤输出的选项而不仅仅是saveXML,所以我想我会提到它。

现在我不完全确定为什么在我的有限测试中对性能的关注我自由地为30,000个节点(20,000个磁道,10,000个音符节点和60,000个属性)循环10,000次,甚至性能给我这些结果相当不错(仅针对下面显示的函数调用,不生成DOM内容,因为这是一个单独的问题):

 $xml->saveXML();
'elapsedTime' => '0.10 seconds',
'elapsedMemory' => '0.39 KB'

 $xml->C14N();
'elapsedTime' => '0.15 seconds',
'elapsedMemory' => '0.3 KB'

  /// outputting to the screen should not be tracked - as I show below this will have a slight, but non-zero impact on the performance benchmarks.
  echo $xml->saveXML()
 'elapsedTime' => '0.16 seconds', //+0.06 seconds
 'elapsedMemory' => '0.3 KB'

  echo $xml->C14N();
 'elapsedTime' => '0.21 seconds', //+0.06 seconds again
 'elapsedMemory' => '0.3 KB'

因此性能略低于saveXML,但在两种情况下我都会说我使用的节点数非常合理。

因此我们可以接受使用saveXML或C14N,我们如何比较这么大的字符串的变化呢?我们应该知道每个人都应该知道。现在马上就会想到md5,但实际上sha1在这里更好,它给我们一个稍微长一点的哈希,性能差异可以忽略不计。在这两种情况下,散列都会增加大约百分之一秒的时间,并且在比较时为我们提供了更容易看到的东西,节省了数据库等。

- 作为旁注,我喜欢哈希,它就像环氧树脂胶,或胶带,它只适用于所有东西。

所以我们简单地哈希,将它保存到变量并将其全部比较:

 print md5( $xml->saveXML() );

'19edc177072416b7bbf88ea0a240be73'
'elapsedTime' => '0.11 seconds',
'elapsedMemory' => '0.39 KB'


 print sha1( $xml->saveXML() );

'7c644c6e1630ffde15eee64643779e415a1746b7'
'elapsedTime' => '0.11 seconds',
'elapsedMemory' => '0.3 KB'

现在我可能会因为使用saveXML()(和/或C14N())而受到打击,但最终归结为它是什么。即使计算可以这种方式完成的属性(只是为了覆盖我的基础):

 $old_xp = new \DOMXpath($xml);
 $old_a = $old_xp->evaluate('count( //@* )');
 $old_n = $old_xp->evaluate('count( //node() )');
 print 'Attributes: '.$old_a.'<br>';
 print 'Nodes: '.$old_n.'<br>';
 print 'Total: '.($old_a + $old_n).'<br>';

输出:/ 1次迭代(检查上面发布的xml):

 Attributes: 6
 Nodes: 7  //expected 4 nodes
 Total: 13

输出:/ 10,000次迭代:

'elapsedTime' => '0.02 seconds',
'elapsedMemory' => '0.5 KB'
 Attributes: 60000
 Nodes: 60001  //expected 30,001 nodes ( +2 tracks, +1 note, node per loop and one album node )?
 Total: 120001

正如您所看到的,时间更快,但是因为我们在这里实例化DOMXpath,如果您已经有可用实例,这可能不会影响您,内存消耗几乎是双倍。

- 作为旁注,似乎$old_xp->evaluate('count( //node() )')给出了我预期4个节点并获得7个节点的奇怪计数,就像它计算开放标签和关闭标签以及编码标签一样,或计算嵌套数对于每个子节点(通过在第二个轨道上添加一个注释节点进行检查,没有,并且计数确实增加了2)。有关此节点的更多信息将会有所帮助。

无论如何,你知道这个方法的其余部分。

但是,在使用计数时,如果要删除1个属性并添加另一个属性,则会错误地计算属性,这同样适用于节点(禁止它进行奇怪的计数)。

但是,最终没有办法知道它是否发生变化,而不是查看实际数据,如果节点内容发生变化会怎样?等...

而且(只是计算)可能足以满足您的需求

选择权属于您自己,这取决于您需要的详细程度,以及您愿意为这一详细程度承担多少性能损失。

我建议对每个步骤进行彻底的基准测试,然后根据您的需要决定哪个更适合。

最后只是生成XML给了我以下时间/内存使用(记住保存和散列只有0.11秒):

'elapsedTime' => '21.16 seconds',
'elapsedMemory' => '0.61 KB'

我们在这里谈论绩效但没有给出数字,所以当我们根据绩效做出决策时,我们确实需要把事情放到上下文中。

谢谢,

答案 2 :(得分:0)

这个一般问题有三种可能的解决方案:

1)按照建议保存副本(或序列化副本)。该解决方案可以完全在外部完成(假设存在克隆或序列化)。但是正如您所提到的,复杂对象的速度可能非常慢。

2)提供服务(更改检测)作为DomDocument(或DomNode)类的功能。此解决方案要求您可以访问DomDocument的定义,因此您可能不想考虑它。但是,这是正确的&#34;因为类本身最了解如何确定是否有任何操作会在DOM中产生变化,所以提供服务以提高效率。

3)提供服务(更改记录)作为上述步骤3的特征。这就是使代码&#34;执行一些操作&#34;跟踪是否进行了更改。对于某些操作,这可能需要保留DOM的一个小的本地化部分的本地副本(或序列化副本)。或者,可能需要在执行操作之前检查一些节点的当前状态,以便确定是否发生了改变。但是,对于大多数操作来说,答案应该是相当明显的。

答案 3 :(得分:0)

嗯,我查看了 DOMDocument 的对象结构。看起来没有简单的方法来做到这一点。我的第一个解决方案是比较方法 saveXML 的结果。但这不是你想要的。让我们加快速度:

变式A:

使用内部序列化 php函数:http://php.net/manual/de/function.serialize.php。这也将比较整个文档,但是以序列化形式。可能比比较saveXML结果快一点。

变式B:

方法 saveXML 的第一个参数很有意思。如果您知道哪些节点可以更改,则可以将输出减少到仅这些节点(请参阅:http://php.net/manual/de/domdocument.savexml.php)。这样您就不必比较整个文档。

变式C:

如果 DOMDocument 对象无法自行确定更改,请让我们这样做!这看起来像这样(没有测试它的语法,但这是重要的想法):

use DOMDocument;
use DOMNode;

class AlterationSensitiveDOMDocument extends DOMDocument
{
    /**
     * @var bool
     *   Determines whether the DOMDocument was altered or not.
     */
    protected $isAltered = false;

    /**
     * @param DOMNode $newnode
     *
     * @return DOMNode|void
     */
    public function appendChild(DOMNode $newnode)
    {
        $this->isAltered = true;

        return parent::appendChild($newnode);
    }

    // More overrides (The ones you need)

    /**
     * @return boolean
     */
    public function isAltered()
    {
        return $this->isAltered;
    }
}

通过使用新的 AlterationSensitiveDOMDocument 而不是 DOMDocument ,您可以完全访问DOMDocument参数和方法。加上一些改变方法(你重写的方法)会将状态“isAltered”设置为true,这样你就可以看到DOMDocument对象是否被改变了。

这个解决方案闻起来像一个黑客,但应该做的伎俩。