PHP和XML - 删除节点

时间:2012-02-26 19:40:03

标签: php xml ajax dom nodes

我创建了一个PHP脚本,它通过AJAX从XML文档构建表。例如:

<bookstore>
  <book>
    <title>Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book>
    <title>Harry Potter</title>
    <author>J K.  Rowling</author>
    <year>2005</year>    
    <price>29.99</price>
  </book>
</bookstore>

将创建一个包含标题,作者,年份和价格列以及其他删除列的表格。在解析XML时,我已将tr id设置为上述情况中当前XML元素(0和1)的tr id。

当我单击删除时,我会使用我要删除的行的ID启动AJAX请求。删除脚本正在接收当前行号而没有问题,但是在尝试删除它时我得到了奇怪的结果。我尝试的当前代码如下(取自http://quest4knowledge.wordpress.com/2010/09/04/php-xml-create-add-edit-modify-using-dom-simplexml-xpath/ 7.2)

if (isset($_POST['rowNumber'])) {

    $rowNumber = $_POST['rowNumber'];
    $file = $_POST['file'];

    $dom = new DOMDocument();
    $dom->load("../XML/".$file);      
    $xml = $dom->documentElement;
    //PROBLEM HERE
    $xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item($rowNumber));

    $handle = fopen("../XML/".$file, 'w');
    fwrite($handle, $dom->saveXML()); 

}

我在页面加载时构建表,然后在每次删除时构建表。麻烦的是,正在删除不正确的行,我无法弄清楚原因。

其他测试......

在50%的点击次数上删除第一个节点:

$xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item(0));

始终删除第一个节点:

$xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item(1));

在50%的点击次数上删除第二个节点:

$xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item(2));

始终删除第二个节点:

$xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item(3));

在50%的点击次数上删除第三个节点:

$xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item(4));

始终删除第三个节点:

$xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item(5));

添加我的AJAX代码

$('#generatedTable a.delete').live('click', function (e) {
    e.preventDefault();

//TABLE ROW ID TO BE DELETED. CAN ALERT THIS FINE.
    var trID = $(this).closest('tr').attr('id'); 

    $.ajax({
        url: "functions/xmlDelete.php", 
        type: "POST",
        dataType: "json",
        data: "rowNumber="+ trID + "&fileName=" + fileName,
        success: function(data) {

            $.ajax({
                url: "functions/xmlParser.php", 
                type: "POST",
                dataType: "json",
                data: "fileName="+ fileName,
                success: function(data) {

                    $('#xmlTable').html(data.table);
                    $('#xmlTable').fadeIn('fast');

                    oTable = $('#generatedTable').dataTable({
                        "bJQueryUI": true,
                        "bPaginate": false,
                        "bLengthChange": false,
                        "bFilter": false,
                        "bSort": false,
                        "bInfo": true
                    }); 

                }      
            }); 

        }      
    });         
} );

这就是我的表行的样子,我可以毫无问题地提醒trID。

<tr id="0" class="odd">
  <td id="0">1999 Grammy Nominees</td>
  <td id="1">Many</td>
  <td id="2">USA</td>
  <td id="3">Grammy</td>
  <td id="4">10.20</td>
  <td id="5">1999</td>
  <td align="center"><a class="edit" href="">Edit</a></td>
  <td align="center"><a class="delete" href="">Delete</a></td>
</tr>

任何人都可以帮助解释我在这里看到的内容。谢谢!

2 个答案:

答案 0 :(得分:2)

如果我理解您正确链接的文档,

 $library->childNodes->item(0)->parentNode

简单地将DOMDocument转换为DOMElement是一种复杂且恼人的方式。您将在更多示例中发现该模式。

现在我们有了一个DOMElement,我们可以在它上面调用removeChild(),它会被传递给DOMElement。这个元素是

$library->childNodes->item(1)

表示第二个元素。

因此,您的代码可能应该如下所示:

$xml->childNodes->item(0)->parentNode->removeChild($xml->childNodes->item($rowNumber));

鉴于$ rowNumber是从0开始的。

答案 1 :(得分:2)

您没有考虑childNodes()包含所有节点,而不仅仅是元素。

对于您提供的xml文档,$dom->documentElement->childNodes->item(0)<bookstore>末尾与<book>开头之间的空白,不是第一个<book> 1}} node。

现在你知道为什么DOM太刺激了。

我建议您使用DOMXPathSimpleXML而不是循环遍历childNodes来收集元素索引。

DOMXPath解决方案

if (isset($_POST['rowNumber'], $_POST['file']) and ctype_digit($_POST['rowNumber'])) {
    $rowNumber = $_POST['rowNumber'];
    $file = '../XML/'.$_POST['file'];

    $dom = new DOMDocument();
    $dom->load($file);

    $root = $dom->documentElement;

    $xp = new DOMXPath($dom);
    $books = $xp->query('*', $root);
    if ($books->item($rowNumber)) {
        $root->removeChild($books->item($rowNumber));
        // Note that "$root->childNodes->item(0)->parentNode" is completely unnecessary.
        // You already have the parent node ($root), so just use it directly!
    } else {
        echo "Row does not exist";
    }

    $dom->save($file);
}

SimpleXML解决方案

if (isset($_POST['rowNumber'], $_POST['file']) and ctype_digit($_POST['rowNumber'])) {
    $rowNumber = (int) $_POST['rowNumber']; // casting to int is necessary!!
    $file = '../XML/'.$_POST['file'];

    $sxe = simplexml_load_file($file);
    unset($sxe->book[$rowNumber]);

    // or, if you don't want to make element name assumptions:
    // $children = $sxe->children();
    // unset($children[$rowNumber]);

    $sxe->asXML($file);
}

问题

但是,整个方法存在两个严重问题。

  1. 您通过file帖子变量接受不受信任的输入。用户无需读写驱动器上的任何XML文件。在处理请求之前,您应该有file必须匹配的有效$_POST['file']值的预定义列表。

  2. 您不考虑并发性。这表现在两个方面:

    1. 如果两个用户同时进行编辑并且一个用户删除了书0,那么当第二个用户删除书0时,他将删除一个与他预期不同的书,即他看来是书1!解决方案是每本书都需要一个明确的唯一标识符,您可以在XML文件和html界面中使用它。您无法使用索引编号来标识节点。
    2. 您对XML文件的读写操作不是原子的,也不使用任何文件锁定。因此,有人可以读取XML,而另一个PHP进程正在写入文件,给读者一个不完整的XML文件。您必须执行原子写入(在* nix系统上,写入唯一的临时文件,然后link()到真实文件名),或者必须使用flock()或其他一些锁定机制打开文件
  3. 因为管理并发读写(特别是以快速方式)是如此困难,所以我建议你放弃这种文件作为数据库的方法,而是使用真正的数据库。

    (顺便提一下,您的HTML无效。id属性在文档中必须是唯一的,但您有两个id="0"。)