PHP和XML-删除节点


PHP and XML - Deleting 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。

当我单击删除时,我会发出一个AJAX请求,其中包含我想要删除的行的ID。删除脚本接收当前行号没有问题,但我在尝试删除它时出现了奇怪的结果。我正在尝试的当前代码如下(取自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>

有人能帮我解释一下我在这里看到了什么吗。谢谢

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

对于您提供的xml文档,$dom->documentElement->childNodes->item(0)<bookstore>结尾和<book>开头之间的空白,而不是第一个<book>节点。

现在您知道为什么DOM如此令人恼火了。

我建议您使用DOMXPathSimpleXML,而不是循环使用childNodes来收集Element索引。

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 post变量接受不受信任的输入。用户无法读取和写入驱动器上的任何XML文件。您应该有一个预定义的有效file值列表,$_POST['file']在处理请求之前必须匹配这些值。

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

    1. 如果两个用户同时编辑,其中一个删除了0本书,那么当第二个删除0本书时,他将删除一本与他想要的不同的书,即,在他看来是1本书!解决方案是,每本书都需要一个明确的、唯一的标识符,您可以在XML文件和html接口中使用该标识符。不能使用索引编号来标识节点
    2. 对XML文件的读写不是原子的,也不使用任何文件锁定。因此,当另一个PHP进程正在写入该文件时,有人可能会读取该XML,从而为读者提供一个不完整的XML文件。您必须执行原子写入(在*nix系统上,写入一个唯一的临时文件,然后link()写入实际文件名),或者必须使用flock()或其他锁定机制打开该文件

由于管理并发读写(尤其是以快速的方式)非常困难,我建议您放弃这种文件即数据库的方法,转而使用真正的数据库。

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

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

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

simple是将DOMDocument转换为DOMElement的一种复杂而令人恼火的方式。你会在更多的例子中发现这种模式。

现在我们有了一个DOMElement,我们可以对它调用removeChild(),它将传递一个DOMEM。这个元素是

$library->childNodes->item(1)

用于第二元件。

所以,你的代码可能应该是这样的:

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

假设$rowNumber是基于0的。