PHP中DOMDocument的缩进

PHP中DOMDocument的缩进,php,xml,whitespace,indentation,domdocument,Php,Xml,Whitespace,Indentation,Domdocument,我正在使用DOMDocument生成一个新的XML文件,我希望该文件的输出能够很好地缩进,以便人类读者能够很容易地理解 例如,当DOMDocument输出此数据时: <?xml version="1.0"?> <this attr="that"><foo>lkjalksjdlakjdlkasd</foo><foo>lkjlkasjlkajklajslk</foo></this> 但这似乎没有任何作用。也许这只适

我正在使用
DOMDocument
生成一个新的XML文件,我希望该文件的输出能够很好地缩进,以便人类读者能够很容易地理解

例如,当
DOMDocument
输出此数据时:

<?xml version="1.0"?>
<this attr="that"><foo>lkjalksjdlakjdlkasd</foo><foo>lkjlkasjlkajklajslk</foo></this>
但这似乎没有任何作用。也许这只适用于读取XML?请记住,我正在尝试编写新文档


DOMDocument
中是否内置了执行此操作的工具?或者是一个可以轻松完成这项任务的函数?

我尝试过以不同的方式运行下面的代码设置
formatOutput
preserveWhiteSpace
,唯一对输出有影响的成员是
formatOutput
。你能运行下面的脚本看看它是否有效吗

<?php
    echo "<pre>";
    $foo = new DOMDocument();
    //$foo->preserveWhiteSpace = false;
    $foo->formatOutput = true;
    $root = $foo->createElement("root");
    $root->setAttribute("attr", "that");
    $bar = $foo->createElement("bar", "some text in bar");
    $baz = $foo->createElement("baz", "some text in baz");
    $foo->appendChild($root);
    $root->appendChild($bar);
    $root->appendChild($baz);
    echo htmlspecialchars($foo->saveXML());
    echo "</pre>";
?>

在John的帮助下,我自己尝试了一下,似乎连DOMDocument对格式的固有支持都不能满足我的需要。因此,我决定编写自己的缩进函数

这是一个非常粗糙的函数,我刚刚很快就把它组合在一起,所以如果有人有任何优化技巧或关于它的任何东西,我很高兴听到

$doc = new DOMDocument('1.0', 'utf-8');
$root = $doc->createElement('root');
$doc->appendChild($root);
函数缩进($text)
{
//必要时创建新行
$find=array('>','0)
$open_标签--;
}
$new_array[]=$tabs$线路;
未结算($tabs);
}
$indented_text=内爆(“\n”,$new_数组);
返回$indented_文本;
}
标题(“内容类型:text/xml”);
$str=”“;
$str=”;
$str=”;
$str=”;
$str=”;
$str=”;
echo$str.=”;

如果使用的扩展名不是
.xml
,请首先将标题
内容类型
标题设置为正确的值。

打印xml时调用哪种方法

我用这个:

$doc->formatOutput = true;
$doc->saveXML($root);
(……)


它工作得很好,但只打印出元素,因此您必须手动打印
部分。

DomDocument将完成此任务,我个人花了几个小时在谷歌上搜索并试图找出答案,我注意到如果您使用

$xmlDoc = new DOMDocument ();
$xmlDoc->preserveWhiteSpace = false;
$xmlDoc->formatOutput = true;
$xmlDoc->loadXML ( $xml );
$xmlDoc->save($archivoxml);
按照这个顺序,它就是不起作用,但是,如果您使用相同的代码,但是按照这个顺序:

$dom = new indentDomDocument("1.0");
$xml = file_get_contents("books.xml");

$dom->loadXML($xml);
$dom->xmlIndent();
echo $dom->saveXML();

class indentDomDocument extends domDocument {
    public function xmlIndent() {
        // Retrieve all text nodes using XPath
        $x = new DOMXPath($this);
        $nodeList = $x->query("//text()");
        foreach($nodeList as $node) {
            // 1. "Trim" each text node by removing its leading and trailing spaces and newlines.
            $node->nodeValue = preg_replace("/^[\s\r\n]+/", "", $node->nodeValue);
            $node->nodeValue = preg_replace("/[\s\r\n]+$/", "", $node->nodeValue);
            // 2. Resulting text node may have become "empty" (zero length nodeValue) after trim. If so, remove it from the dom.
            if(strlen($node->nodeValue) == 0) $node->parentNode->removeChild($node);
        }
        // 3. Starting from root (documentElement), recursively indent each node. 
        $this->xmlIndentRecursive($this->documentElement, 0);
    } // end function xmlIndent

    private function xmlIndentRecursive($currentNode, $depth) {
        $indentCurrent = true;
        if(($currentNode->nodeType == XML_TEXT_NODE) && ($currentNode->parentNode->childNodes->length == 1)) {
            // A text node being the unique child of its parent will not be indented.
            // In this special case, we must tell the parent node not to indent its closing tag.
            $indentCurrent = false;
        }
        if($indentCurrent && $depth > 0) {
            // Indenting a node consists of inserting before it a new text node
            // containing a newline followed by a number of tabs corresponding
            // to the node depth.
            $textNode = $this->createTextNode("\n" . str_repeat("\t", $depth));
            $currentNode->parentNode->insertBefore($textNode, $currentNode);
        }
        if($currentNode->childNodes) {
            $indentClosingTag = false;
            foreach($currentNode->childNodes as $childNode) $indentClosingTag = $this->xmlIndentRecursive($childNode, $depth+1);
            if($indentClosingTag) {
                // If children have been indented, then the closing tag
                // of the current node must also be indented.
                $textNode = $this->createTextNode("\n" . str_repeat("\t", $depth));
                $currentNode->appendChild($textNode);
            }
        }
        return $indentCurrent;
    } // end function xmlIndentRecursive

} // end class indentDomDocument

工作起来很有魅力,希望这有助于

本主题中的大多数答案处理xml文本流。 下面是使用dom功能执行缩进作业的另一种方法。 loadXML()dom方法将xml源中的缩进字符作为文本节点导入。其思想是从dom中删除这些文本节点,然后重新创建格式正确的文本节点(有关更多详细信息,请参阅下面代码中的注释)

xmlIndent()函数作为从domDocument继承的indentDomDocument类的方法实现。 下面是如何使用它的完整示例:

$x = new \DOMDocument;
$x -> preserveWhiteSpace = false;
$x -> formatOutput = true;
$x -> loadXML('<root>a<b>c</b></root>');
echo $x -> saveXML();
你瞧

刚刚发现,显然,根XML元素可能不包含文本子元素。这是非直观的a。F但显然,这就是为什么,比如

$x=new\DOMDocument;
$x->preserveWhiteSpace=false;
$x->formatOutput=true;
$x->loadXML('ac');
echo$x->saveXML();
将无法缩进


好了,h。THet c.

您的代码运行良好,但我的设置方式不适合我。我有一个类xml,在该类中我创建了一个变量$this->xml,它包含DOMDocument的一个实例,但它似乎不适用于该设置。我也希望有真正的标签,而不仅仅是空格。这似乎是一种特殊情况。我用“xml”作为成员创建了一个简单的类,它仍然有效。有太多的因素,如果没有你的确切代码(或者一个简化版本,仍然无法为你),它将无法复制。谢谢你的帮助John。我已经编写了一个基本的缩进函数,希望它能解决我的问题(如果你想看的话,我将把它作为一个答案发布)。一句简短的评论:有str_repeat()用于创建选项卡。函数的其余部分对我来说似乎很好。您可以设置一个与我发现的性能比较。作为另一种选择,您可以使用strtok()以迭代方式对输入进行标记化(而不是替换/分解)。谢谢!事实上,我比我自己更喜欢你找到的函数,因为我发现你越深入,它的格式就越糟糕。我从来都不知道str_repeat()或strtok(),所以也谢谢你!这里有一个非常简单的函数(基于正则表达式):我不确定问题是什么。您显示的代码将给出您要求的输出。证明:和-你是在问缩进级别,例如使用的空格数吗?只要缩进是相关的:都德!你太棒了!谢谢你发现这个!该死的。。。这似乎只适用于XML,HTML看起来仍然很难看=/我不确定您是如何体验到这一点的,因为即使在从5.0.0一直到7.0.5的所有PHP版本中首先调用
loadXML()
时,格式也会起作用:@Benjamin它仅在某些情况下在首先运行
loadXML()
时才能正确格式化。其他时候,它将无法修复空白@MikeChelen的斑点!
$xmlDoc = new DOMDocument ();
$xmlDoc->loadXML ( $xml );
$xmlDoc->preserveWhiteSpace = false;
$xmlDoc->formatOutput = true;
$xmlDoc->save($xml_file);
$xmlDoc = new DOMDocument ();
$xmlDoc->preserveWhiteSpace = false;
$xmlDoc->formatOutput = true;
$xmlDoc->loadXML ( $xml );
$xmlDoc->save($archivoxml);
$dom = new indentDomDocument("1.0");
$xml = file_get_contents("books.xml");

$dom->loadXML($xml);
$dom->xmlIndent();
echo $dom->saveXML();

class indentDomDocument extends domDocument {
    public function xmlIndent() {
        // Retrieve all text nodes using XPath
        $x = new DOMXPath($this);
        $nodeList = $x->query("//text()");
        foreach($nodeList as $node) {
            // 1. "Trim" each text node by removing its leading and trailing spaces and newlines.
            $node->nodeValue = preg_replace("/^[\s\r\n]+/", "", $node->nodeValue);
            $node->nodeValue = preg_replace("/[\s\r\n]+$/", "", $node->nodeValue);
            // 2. Resulting text node may have become "empty" (zero length nodeValue) after trim. If so, remove it from the dom.
            if(strlen($node->nodeValue) == 0) $node->parentNode->removeChild($node);
        }
        // 3. Starting from root (documentElement), recursively indent each node. 
        $this->xmlIndentRecursive($this->documentElement, 0);
    } // end function xmlIndent

    private function xmlIndentRecursive($currentNode, $depth) {
        $indentCurrent = true;
        if(($currentNode->nodeType == XML_TEXT_NODE) && ($currentNode->parentNode->childNodes->length == 1)) {
            // A text node being the unique child of its parent will not be indented.
            // In this special case, we must tell the parent node not to indent its closing tag.
            $indentCurrent = false;
        }
        if($indentCurrent && $depth > 0) {
            // Indenting a node consists of inserting before it a new text node
            // containing a newline followed by a number of tabs corresponding
            // to the node depth.
            $textNode = $this->createTextNode("\n" . str_repeat("\t", $depth));
            $currentNode->parentNode->insertBefore($textNode, $currentNode);
        }
        if($currentNode->childNodes) {
            $indentClosingTag = false;
            foreach($currentNode->childNodes as $childNode) $indentClosingTag = $this->xmlIndentRecursive($childNode, $depth+1);
            if($indentClosingTag) {
                // If children have been indented, then the closing tag
                // of the current node must also be indented.
                $textNode = $this->createTextNode("\n" . str_repeat("\t", $depth));
                $currentNode->appendChild($textNode);
            }
        }
        return $indentCurrent;
    } // end function xmlIndentRecursive

} // end class indentDomDocument
$x = new \DOMDocument;
$x -> preserveWhiteSpace = false;
$x -> formatOutput = true;
$x -> loadXML('<root>a<b>c</b></root>');
echo $x -> saveXML();