在 PHP 中使用 DOMDocument 缩进

Posted

技术标签:

【中文标题】在 PHP 中使用 DOMDocument 缩进【英文标题】:Indentation with DOMDocument in PHP 【发布时间】:2010-10-19 06:37:02 【问题描述】:

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

比如DOMDocument输出这个数据时:

<?xml version="1.0"?>
<this attr="that"><foo>lkjalksjdlakjdlkasd</foo><foo>lkjlkasjlkajklajslk</foo></this>

我希望 XML 文件是:

<?xml version="1.0"?>
<this attr="that">
    <foo>lkjalksjdlakjdlkasd</foo>
    <foo>lkjlkasjlkajklajslk</foo>
</this>

我一直在四处寻找答案,我发现的一切似乎都在试图以这种方式控制空白:

$foo = new DOMDocument();
$foo->preserveWhiteSpace = false;
$foo->formatOutput = true;

但这似乎没有任何作用。也许这仅在读取 XML 时有效?请记住,我正在尝试编写新文档。

DOMDocument 有内置的功能吗?或者有什么功能可以轻松完成?

【问题讨论】:

这里有一个很好的简单函数(基于正则表达式):Format XML with php 我不确定问题是什么。您显示的代码将给出您要求的输出。证明:codepad.org/4UGyRspx 和 codepad.org/bLTOFQrp - 您是否询问缩进级别,例如使用了多少个空格? 只要涉及缩进就相关:Converting indentation with preg_replace (no callback) 【参考方案1】:

DomDocument 可以解决问题,我个人花了几个小时谷歌搜索并试图弄清楚这一点,我注意到如果你使用

$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);

就像一个魅力,希望这会有所帮助

【讨论】:

老兄!你摇滚!感谢您发现这一点! 该死...这似乎只适用于 XML,html 看起来仍然很丑。 =/ 我不知道你是如何体验到这一点的,因为即使在从 5.0.0 一直到 7.0.5 的所有 PHP 版本中,即使首先调用 loadXML(),格式也会起作用:3v4l.org/QrLlo @Benjamin 只有在某些情况下,当loadXML() 首先运行时,它才能正确格式化。其他时候它将无法修复空格3v4l.org/Qt9EV @MikeChelen 很好发现!【参考方案2】:

在 John 的一些帮助和我自己玩弄这个之后,似乎即使 DOMDocument 对格式化的固有支持也不能满足我的需求。所以,我决定编写自己的缩进函数。

这是一个非常粗略的函数,我只是快速拼凑而成,所以如果有人有任何优化技巧或关于它的总体看法,我会很高兴听到它!

function indent($text)

    // Create new lines where necessary
    $find = array('>', '</', "\n\n");
    $replace = array(">\n", "\n</", "\n");
    $text = str_replace($find, $replace, $text);
    $text = trim($text); // for the \n that was added after the final tag

    $text_array = explode("\n", $text);
    $open_tags = 0;
    foreach ($text_array AS $key => $line)
    
        if (($key == 0) || ($key == 1)) // The first line shouldn't affect the indentation
            $tabs = '';
        else
        
            for ($i = 1; $i <= $open_tags; $i++)
                $tabs .= "\t";
        

        if ($key != 0)
        
            if ((strpos($line, '</') === false) && (strpos($line, '>') !== false))
                $open_tags++;
            else if ($open_tags > 0)
                $open_tags--;
        

        $new_array[] = $tabs . $line;

        unset($tabs);
    
    $indented_text = implode("\n", $new_array);

    return $indented_text;

【讨论】:

简短说明:有 str_repeat() 用于创建选项卡。其余的功能对我来说似乎还可以。您可以与我找到的那个进行一个小的性能比较。作为另一种想法,您可以使用 strtok() 迭代地标记输入(而不是替换/分解)。 谢谢!实际上,我比我自己更喜欢您找到的功能,因为我发现您越深入,它的格式设置就越差。而且我从来不知道 str_repeat() 或 strtok(),所以也谢谢你!【参考方案3】:

我尝试以不同的方式运行下面的代码设置formatOutputpreserveWhiteSpace,唯一对输出有任何影响的成员是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>";
?>

【讨论】:

您的代码工作正常,但它不适用于我设置它的方式。我有一个类 xml,在该类中我创建了一个变量 $this->xml,它包含一个 DOMDocument 的实例,它似乎不适用于该设置。我也希望有真正的标签而不是空格。 这似乎是一个特例。我创建了一个以“xml”为成员的简单类,它仍然有效。有太多因素,如果没有您的确切代码(或对您来说仍然失败的简化版本),将无法重现。 感谢约翰的帮助。我已经编写了一个基本的缩进函数,希望能解决我的问题(如果你想看一下,将把它作为答案发布)。【参考方案4】:

打印xml的时候调用什么方法?

我用这个:

$doc = new DOMDocument('1.0', 'utf-8');
$root = $doc->createElement('root');
$doc->appendChild($root);

(...)

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

它工作得很好,但只打印出元素,所以你必须手动打印&lt;?xml ... ?&gt; 部分..

【讨论】:

【参考方案5】:

本主题中的大多数答案都涉及 xml 文本流。 这是另一种使用 dom 功能来执行缩进工作的方法。 loadXML() dom 方法将 xml 源中存在的缩进字符导入为文本节点。想法是从 dom 中删除此类文本节点,然后重新创建格式正确的文本节点(有关详细信息,请参阅下面代码中的 cmets)。

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

$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

【讨论】:

【参考方案6】:

你偷看,

刚刚发现,一个根 XML 元素可能不包含文本子元素。这是不直观的。 F。但显然,这就是原因,例如,

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

将无法缩进。

https://bugs.php.net/bug.php?id=54972

所以你去,h。吨。 H。等等。

【讨论】:

【参考方案7】:
header("Content-Type: text/xml");

$str = "";
$str .= "<customer>";
$str .= "<offer>";
$str .= "<opened></opened>";
$str .= "<redeemed></redeemed>";
$str .= "</offer>";
echo $str .= "</customer>";

如果您使用除.xml 以外的任何扩展名,则首先将标头Content-Type 标头设置为正确的值。

【讨论】:

以上是关于在 PHP 中使用 DOMDocument 缩进的主要内容,如果未能解决你的问题,请参考以下文章

来自DOMDocument的nodeValue在PHP中返回奇怪的字符

使用 DOMDocument PHP 获取 Xpath 父节点?

在 PHP 中从 DOMNode 创建 DOMDocument

在 PHP 中,使用 DomDocument getElementByID 不起作用?我究竟做错了啥?

PHP 4 中的新 DOMDocument()

如何防止 PHP 的 DOMDocument 编码 html 实体?