2016-06-14 115 views
3

我有一个看起来像这样的XML文档:如何克隆PHP中没有数据的独特XML结构?

<root> 

    <node/> 

    <node> 
    <sub>more</sub> 
    </node> 

    <node> 
    <sub>another</sub> 
    </node> 

    <node>value</node> 

</root> 

这里是我的伪代码:

import xml. 

create empty-xml. 

foreach child of imported-xml-root-node, 

    recursively clone node structure without data. 

    if clone does not match one already in empty-xml, 
     then add clone to empty-xml. 

我想要得到的结果,看起来像这样:

<root> 

    <node/> 

    <node> 
    <sub/> 
    </node> 

</root> 

注意我的piddly示例数据只有3个节点深。在生产中,会有未知数量的后代,因此可接受的答案需要处理变量节点深度。


失败途径

我已审查The DOMNode class具有cloneNode法,我想用递归选项,但它会采取一些额外的工作来清除数据。但是,虽然该类包含一个hasChildNodes函数,它返回一个布尔值,但是我找不到实际返回子集合的方法。

$doc = new DOMDocument(); 
$doc->loadXML($xml); 

$root_node = $doc->documentElement; 

if ($root_node->hasChildNodes()) { 

    // looking for something like this: 
    // foreach ($root_node->children() as $child) 
    // $doppel = $child->cloneNode(true); 

} 

其次,我已经试过我的手与The SimpleXMLElement class它确实有一个真棒children方法。虽然它没有递归选项,但我建立了一个简单的函数来克服它。但是这个类缺少一个clone/copyNode方法,而且我的函数膨胀成了一些令人讨厌的补偿。现在我正在考虑结合使用这两个类,所以我可以访问SimpleXMLElement::childrenDOMDocument::cloneNode,但我可以告诉这不是干净利落,当然这个问题可以更好地解决。

$sxe = new SimpleXMLElement($xml); 

$indentation = 0; 

function getNamesRecursive($xml, &$indentation) 
{ 
    $indentation++; 
    foreach($xml->children() as $child) { 
     for($i=0;$i<$indentation;$i++) 
      echo "\t"; 
     echo $child->getName() . "\n"; 
     getNamesRecursive($child,$indentation); 
    } 
    $indentation--; 
} 

getNamesRecursive($sxe,$indentation); 
+0

便携式根节点的选择:'$ doc-> documentElement' – rjdown

+0

@rjdown哦真棒,谢谢!我编辑了我的问题来解决这个问题。很高兴知道! –

+1

[DOMNode :: $ childNodes](http://php.net/manual/de/class.domnode.php#domnode.props.childnodes)是属性,而不是方法。 – ThW

回答

0

好吧,这里是我的臭的解决方案。 suggestions for improvements或全新的更好的答案仍然非常受欢迎。

$xml = ' 
<root> 
    <node/> 
    <node> 
    <sub>more</sub> 
    </node> 
    <node> 
    <sub>another</sub> 
    </node> 
    <node>value</node> 
</root> 
'; 
$doc = new DOMDocument(); 
$doc->loadXML($xml); 


// clone without data 
$empty_xml = new DOMDocument(); 
$empty_xml->appendChild($empty_xml->importNode($doc->documentElement)); 
function clone_without_data(&$orig, &$clone, &$clonedoc){ 
    foreach ($orig->childNodes as $child){ 
    if(get_class($child) === "DOMElement") 
     $new_node = $clone->appendChild($clonedoc->importNode($child)); 
    if($child->hasChildNodes()) 
     clone_without_data($child,$new_node,$clonedoc); 
    } 
} 
clone_without_data($doc->documentElement, $empty_xml->documentElement, $empty_xml); 


// remove all duplicates 
$distinct_structure = new DOMDocument(); 
$distinct_structure->appendChild($distinct_structure->importNode($doc->documentElement)); 
foreach ($empty_xml->documentElement->childNodes as $child){ 
    $match = false; 
    foreach ($distinct_structure->documentElement->childNodes as $i => $element){ 
    if ($distinct_structure->saveXML($element) === $empty_xml->saveXML($child)) { 
     $match = true; 
     break; 
    } 
    } 
    if (!$match) 
    $distinct_structure->documentElement->appendChild($distinct_structure->importNode($child,true)); 
} 
$distinct_structure->formatOutput = true; 
echo $distinct_structure->saveXML(); 

导致这样的输出:

<?xml version="1.0"?> 
<root> 
    <node/> 
    <node> 
    <sub/> 
    </node> 
</root> 
1

考虑XSLT,设计来转换XML文件中的专用语言。而PHP维护一个XSLT 1.0处理器。您只需保留位置1的项目并仅复制其元素而不是文本。

XSLT(保存为文件的.xsl在PHP下面使用)

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> 
<xsl:output version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" /> 
<xsl:strip-space elements="*"/> 

    <!-- Identity Transform --> 
    <xsl:template match="@*|node()"> 
    <xsl:copy>  
     <xsl:apply-templates select="@*|node()"/>  
    </xsl:copy> 
    </xsl:template> 

    <!-- Remove any nodes position greater than 2 --> 
    <xsl:template match="*[position() &gt; 2]"/> 

    <!-- Copy only tags --> 
    <xsl:template match="/*/*/*"> 
    <xsl:copy/> 
    </xsl:template> 

</xsl:transform> 

PHP

// LOAD XML AND XSL FILES 
$xml = new DOMDocument('1.0', 'UTF-8'); 
$xml->load('Input.xml'); 

$xslfile = new DOMDocument('1.0', 'UTF-8'); 
$xslfile->load('Script.xsl'); 

// TRANSFORM XML with XSLT 
$proc = new XSLTProcessor; 
$proc->importStyleSheet($xslfile); 
$newXml = $proc->transformToXML($xml); 

// ECHO OUTPUT STRING 
echo $newXml; 
# <root> 
# <node/> 
# <node> 
#  <sub/> 
# </node> 
# </root> 

// NEW DOM OBJECT 
$final = new DOMDocument('1.0', 'UTF-8'); 
$final->loadXML($newXml); 
+0

哇,这看起来很简单。我迫不及待地想要在早上试试它:)谢谢!顺便说一句,你是怎么偶然发现我的问题?我确信它现在被埋了。 –

+0

它适合你吗? – Parfait

+0

我真的很喜欢这种优雅,所以我现在对XSL有很多了解,然而,似乎'match =“/ */*/*”'限制为3个节点。只使用'*'只能给我一个根。我现在正在修补“删除大于2的任何节点位置”部分。我的实际使用案例将有未知数量的节点深度,因此是我的递归策略。 –