PHP 排序和更新节点


PHP sort and update nodes

我整天都在为此作斗争:(

虽然我找到了类似问题的答案,但它们不会更新现有的 XML,而是创建一个新的 XML。

任何帮助将不胜感激。

这是我正在加载并尝试仅对图像>图像节点进行排序的 XML:

<?xml version="1.0"?>
<stuff>
    <other_nodes>
    </other_nodes>
    <images>
        <image><sorted_number><![CDATA[1]]></sorted_number></image>
        <image><sorted_number><![CDATA[3]]></sorted_number></image>
        <image><sorted_number><![CDATA[2]]></sorted_number></image>
    </images>
</stuff>
//load the xml into a var
$theXML = //load the xml from the database
$imageNode = $theXML->images;
//sort the images into sorted order
$d = $imageNode;
// turn into array
$e = array();
foreach ($d->image as $image) {
        $e[] = $image;
}
// sort the array
usort($e, function($a, $b) {
        return $a->sorted_number - $b->sorted_number;
});

//now update the xml in the correct order
foreach ($e as $node) { 
//???unsure how to update the images node in my XML
}

SimpleXML 对于您的任务来说太简单了。没有简单的方法来对节点进行重新排序。基本上,在排序例程之后,您必须重建<image>节点,但是您内部有CDATA并且 SimpleXML 不能直接添加CDATA值。

如果你想尝试这种方式,在这里你可以找到一个很酷的SimpleXML类扩展,它添加了CDATA属性,而且这个解决方案也使用DOMDocument

基本上,恕我直言,由于每个解决方案都需要 DOM,最好的方法是直接使用 DOMDocument,并最终在转换后使用 SimpleXML (重新)加载 XML:

$dom = new DOMDocument();
$dom->loadXML( $xml, LIBXML_NOBLANKS );
$dom->formatOutput = True;
$images = $dom->getElementsByTagName( 'image' );
/* This is the same as your array conversion: */
$sorted = iterator_to_array( $images );
/* This is your sorting routine adapted to DOMDocument: */
usort( $sorted, function( $a, $b )
{
    return
    $a->getElementsByTagName('sorted_number')->item(0)->nodeValue
    -
    $b->getElementsByTagName('sorted_number')->item(0)->nodeValue;
});
/* This is the core loop to “replace” old nodes: */
foreach( $sorted as $node ) $images->item(0)->parentNode->appendChild( $node );
echo $dom->saveXML();

IDEe演示

主例程将排序的节点作为子节点添加到现有的<images>节点。请注意,没有必要预先删除旧子项:由于我们引用了相同的对象,因此实际上通过附加一个节点,我们将其从之前的位置删除。

如果要获取 SimpleXML 对象,可以在上述代码的末尾附加以下行:

$xml = simplexml_load_string( $dom->saveXML() );

考虑一个使用其<xsl:sort>的 XSLT 解决方案。作为信息,XSLT(其脚本是格式正确的 XML 文件)是一种声明性的专用编程语言(与 SQL 类型相同),专门用于操作 XML 文档,排序是一种操作类型。XSLT 通常用作样式表将 XML 内容呈现为 HTML,实际上是一种语言。

大多数通用语言,包括PHP(xsl扩展),Python(lxml模块),Java(javax.xml),Perl(libxml),C#(System.Xml)和VB(MSXML)都维护XSLT 1.0处理器。并且各种外部可执行处理器,如Xalan和Saxon(后者可以运行XSLT 2.0和最近的3.0)也可用 - 当然PHP可以使用exec()调用。下面将 XSLT 嵌入为字符串变量,但可以很容易地从外部 .xsl 或 .xslt 文件加载。

// Load the XML source and XSLT file
$doc = new DOMDocument();
$doc->loadXML($xml);
$xsl = new DOMDocument;    
$xslstr = '<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
           <xsl:output version="1.0" encoding="UTF-8" indent="yes"
                cdata-section-elements="sorted_number" />
           <xsl:strip-space elements="*"/>
           <!-- IDENTITY TRANSFORM (COPIES ALL CONTENT AS IS) -->
           <xsl:template match="@*|node()">
             <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
             </xsl:copy>
           </xsl:template>  
           <!-- SORT IMAGE CHILDREN IN EACH IMAGES NODE -->
           <xsl:template match="images">
             <xsl:copy>
                <xsl:apply-templates select="image">
                    <xsl:sort select="sorted_number" order="ascending" data-type="number"/>
                </xsl:apply-templates>
             </xsl:copy>
           </xsl:template>               
           </xsl:transform>';
$xsl->loadXML($xslstr);
// Configure the processor
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl); 
// Transform XML source
$newXml = $proc->transformToXML($doc);    
echo $newXml;

结果(通知<![CData[]]>保留)

<?xml version="1.0" encoding="UTF-8"?>
<stuff>
  <other_nodes/>
  <images>
    <image>
      <sorted_number><![CDATA[1]]></sorted_number>
    </image>
    <image>
      <sorted_number><![CDATA[2]]></sorted_number>
    </image>
    <image>
      <sorted_number><![CDATA[3]]></sorted_number>
    </image>
  </images>
</stuff>

在深入探讨之前,是否真的需要保存排序状态?就像在数据库中一样,您始终可以在检索项目时对项目进行排序,此处与您已经编写的代码相同。

也就是说,在您的情况下,"更新"意味着删除所有<image>节点并按顺序重新添加它们。

更新
参见Fusion3K的答案,没有必要删除节点,只需附加它们即可。我建议采用他的解决方案。

您正在使用 SimpleXml ,它不提供复制节点的方法。您将需要重新创建每个节点、子节点、属性。你的XML看起来很简单,但我想这是一个示例,而你的实际XML更复杂。然后改用DOM及其importNode()方法,该方法可以复制复杂节点,包括其所有属性和子节点。

另一方面,对我来说SimpleXml感觉容易得多,所以我将两者结合起来:

$xml = simplexml_load_string($x); // assume XML in $x
$images = $xml->xpath("/stuff/images/image");
usort($images, function ($a, $b){
    return strnatcmp($a->sorted_number, $b->sorted_number);
});

评论:

  • xpath() 是一种将所有项目放入 SimpleXml 对象数组中的快速方法。
  • $images现在已排序,但我们无法删除原始节点,因为$images保存对这些节点的引用。

这就是为什么我们需要将$images保存到一个新的临时文档中。

$tmp = new DOMDocument('1.0', 'utf-8');
$tmp->loadXML("<images />"); 
// add image to $tmp, then delete it from $xml
foreach($images as $image) { 
    $node = dom_import_simplexml($image); // make DOM from SimpleXml
    $node = $tmp->importNode($node, TRUE); // import and append in $tmp
    $tmp->getElementsByTagName("images")->item(0)->appendChild($node);
    unset($image[0]); // delete image from $xml
}  

评论:

  • 现在使用 DOM,因为我可以使用importNode()复制节点
  • 此时,$tmp按所需顺序拥有所有<image>节点,$xml没有节点。

要将节点从$tmp复制回$xml,我们需要将$xml导入到DOM

$xml = dom_import_simplexml($xml)->ownerDocument;
foreach($tmp->getElementsByTagName('image') as $image) {
    $node = $xml->importNode($image, TRUE);
    $xml->getElementsByTagName("images")->item(0)->appendChild($node);
}  
// output...
echo $xml->saveXML();

查看实际操作:https://eval.in/535800