我创建了一个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如此令人恼火了。
我建议您使用DOMXPath
或SimpleXML
,而不是循环使用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);
}
问题
然而,你的整个方法有两个严重的问题。
您正在通过
file
post变量接受不受信任的输入。用户无法读取和写入驱动器上的任何XML文件。您应该有一个预定义的有效file
值列表,$_POST['file']
在处理请求之前必须匹配这些值。您不考虑并发性。这体现在两个方面:
- 如果两个用户同时编辑,其中一个删除了0本书,那么当第二个删除0本书时,他将删除一本与他想要的不同的书,即,在他看来是1本书!解决方案是,每本书都需要一个明确的、唯一的标识符,您可以在XML文件和html接口中使用该标识符。不能使用索引编号来标识节点
- 对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的。