【问题标题】:Indentation with DOMDocument in PHP在 PHP 中使用 DOMDocument 缩进
【发布时间】: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 有内置的功能吗?或者有什么功能可以轻松完成?

【问题讨论】:

标签: php xml whitespace indentation domdocument


【解决方案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 标头设置为正确的值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多