如何对使用SimpleXML加载的XML文件的内容进行排序


How to sort content of an XML file loaded with SimpleXML?

存在一个内容类似于以下内容的XML文件:

<FMPDSORESULT xmlns="http://www.filemaker.com">
<ERRORCODE>0</ERRORCODE>
<DATABASE>My_Database</DATABASE>
<LAYOUT/>
<ROW MODID="1" RECORDID="1">
    <Name>John</Name>
    <Age>19</Age>
</ROW>
<ROW MODID="2" RECORDID="2">
    <Name>Steve</Name>
    <Age>25</Age>
</ROW>
<ROW MODID="3" RECORDID="3">
    <Name>Adam</Name>
    <Age>45</Age>
</ROW>

我尝试使用array_multisort函数按Name标签的值对ROW标签进行排序:

$xml = simplexml_load_file( 'xml1.xml');
$xml2 = sort_xml( $xml  );
print_r( $xml2 );
function sort_xml( $xml  ) {
    $sort_temp = array();
    foreach ( $xml as $key => $node ) {
        $sort_temp[ $key ] = (string) $node->Name;
}
array_multisort( $sort_temp, SORT_DESC,  $xml );
return $xml;
}

我建议使用DOM扩展,因为它更灵活:

$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$doc->load('xml1.xml');
// Get the root node
$root = $doc->getElementsByTagName('FMPDSORESULT');
if (!$root->length)
  die('FMPDSORESULT node not found');
$root = $root[0];
// Pull the ROW tags from the document into an array.
$rows = [];
$nodes = $root->getElementsByTagName('ROW');
while ($row = $nodes->item(0)) {
  $rows []= $root->removeChild($row);
}
// Sort the array of ROW tags
usort($rows, function ($a, $b) {
  $a_name = $a->getElementsByTagName('Name');
  $b_name = $b->getElementsByTagName('Name');
  return ($a_name->length && $b_name->length) ?
    strcmp(trim($a_name[0]->textContent), trim($b_name[0]->textContent)) : 0;
});
// Append ROW tags back into the document
foreach ($rows as $row) {
  $root->appendChild($row);
}
// Output the result
echo $doc->saveXML();
输出

<?xml version="1.0"?>
<FMPDSORESULT xmlns="http://www.filemaker.com">
  <ERRORCODE>0</ERRORCODE>
  <DATABASE>My_Database</DATABASE>
  <LAYOUT/>
  <ROW MODID="3" RECORDID="3">
    <Name>Adam</Name>
    <Age>45</Age>
  </ROW>
  <ROW MODID="1" RECORDID="1">
    <Name>John</Name>
    <Age>19</Age>
  </ROW>
  <ROW MODID="2" RECORDID="2">
    <Name>Steve</Name>
    <Age>25</Age>
  </ROW>
</FMPDSORESULT>
关于XPath

您可以使用DOMXPath进行更灵活的遍历。然而,在这个特定的问题中,在我看来,使用DOMXPath不会带来显著的改善。无论如何,为了完整性,我将给出一些例子。

获取行:

$xpath = new DOMXPath($doc);
$xpath->registerNamespace('myns', 'http://www.filemaker.com');
$rows = [];
foreach ($xpath->query('//myns:ROW') as $row) {
  $rows []= $row->parentNode->removeChild($row);
}

将行追加回文档:

$root = $xpath->evaluate('/myns:FMPDSORESULT')[0];
foreach ($rows as $row) {
  $root->appendChild($row);
}

一些SimpleXMLElement方法返回数组,但大多数返回实现Iterator的SimpleXMLElement对象。var_dump()将只以简化的表示方式显示部分数据。然而,它是一个对象结构,而不是一个嵌套数组。

如果我理解正确的话,您想按Name子元素对ROW元素进行排序。您可以使用xpath()方法获取它们,但是您需要为名称空间注册一个前缀。它返回一个SimpleXMLElement对象数组。数组可以用usort排序。

$fResult = new SimpleXMLElement($xml);
$fResult->registerXpathNamespace('fm', 'http://www.filemaker.com');
$rows = $fResult->xpath('//fm:ROW');
usort(
  $rows,
  function(SimpleXMLElement $one, SimpleXMLElement $two) {
    return strcasecmp($one->Name, $two->Name);
  }
);
var_dump($rows);

在DOM中看起来不会有太大的不同,但是DOMXpath::evaluate()返回一个DOMNodeList。您可以使用iterator_to_array将其转换为数组。

$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$xpath->registerNamespace('fm', 'http://www.filemaker.com');
$rows = iterator_to_array($xpath->evaluate('//fm:ROW'));
usort(
  $rows,
  function(DOMElement $one, DOMElement $two) use ($xpath) {
    return strcasecmp(
      $xpath->evaluate('normalize-space(Name)', $one),
      $xpath->evaluate('normalize-space(Name)', $two)
    );
  }
);
var_dump($rows);

DOM没有访问子元素和值的神奇方法,可以使用Xpath来获取它们。Xpath函数string()将第一个节点转换为字符串。如果节点列表为空,则返回空字符串。normalize-space()做了更多。它用一个空格替换所有的空白组,并从字符串的开始和结束处删除它。