Отступ с DOMDocument в PHP

Я использую 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 для этого? Или функция, которая может легко это сделать?


person Josh Leitzel    schedule 14.04.2009    source источник
comment
Здесь есть хорошая простая функция (основанная на регулярных выражениях): Форматировать XML с помощью PHP   -  person Tomalak    schedule 14.04.2009
comment
Я не уверен, в чем вопрос. Код, который вы показываете, даст результат, который вы запрашиваете. Доказательство: codepad.org/4UGyRspx и codepad.org/bLTOFQrp — вы спрашиваете об уровне отступа, например количество используемых мест?   -  person Gordon    schedule 04.03.2012


Ответы (7)


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

Работает как шарм, надеюсь, это поможет

person Angel    schedule 25.01.2013
comment
Чувак! Ты жжешь! Спасибо, что заметили это! - person Alix Axel; 16.06.2013
comment
Черт возьми... Кажется, это работает только с XML, HTML по-прежнему выглядит уродливо. знак равно - person Alix Axel; 16.06.2013
comment
Я не уверен, как вы могли испытать это, так как форматирование работает даже при вызове loadXML() во всех версиях PHP от 5.0.0 до 7.0.5: 3v4l.org/QrLlo - person BenMorel; 02.04.2016
comment
@Benjamin Он правильно форматирует только в определенных случаях, когда loadXML() запускается первым. В других случаях не удается исправить пробелы 3v4l.org/Qt9EV. - person Mike Chelen; 01.06.2016
comment
@MikeChelen Хорошо подмечено! - person BenMorel; 02.06.2016
comment
В конце концов, для HTML вы можете использовать dindent, работает хорошо - person saq; 06.01.2017
comment
@Botea, пожалуйста, удалите свой комментарий и задайте новый полный вопрос. - person mickmackusa; 24.05.2018

После некоторой помощи Джона и самостоятельной игры с этим, кажется, что даже встроенная в 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;
}
person Josh Leitzel    schedule 14.04.2009
comment
Небольшое замечание: для создания вкладок существует функция str_repeat(). Остальные функции мне кажутся вполне приемлемыми. Вы можете настроить небольшое сравнение производительности с тем, что я нашел. В качестве альтернативной идеи вы можете использовать strtok() для итеративной маркировки ввода (вместо замены/разрыва). - person Tomalak; 14.04.2009
comment
Спасибо! На самом деле функция, которую вы нашли, мне нравится больше, чем моя, так как я обнаружил, что чем глубже вы идете, тем хуже форматирование. И я никогда не знал ни о str_repeat(), ни о strtok(), так что спасибо и за это! - person Josh Leitzel; 21.04.2009

Я пробовал запускать приведенный ниже код, устанавливая 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>";
?>
person John Rasch    schedule 14.04.2009
comment
Ваш код работает нормально, но у меня он не работает с тем, как я его настроил. У меня есть класс xml, и внутри этого класса я создаю переменную $this-›xml, которая содержит экземпляр DOMDocument, и, похоже, она не работает с этой настройкой. Я также предпочел бы иметь настоящие вкладки, а не просто пробелы. - person Josh Leitzel; 14.04.2009
comment
Тогда это похоже на частный случай. Я создал простой класс с xml в качестве члена, и он все еще работал. Слишком много факторов, и без вашего точного кода (или упрощенной версии, которая все еще не работает) воспроизвести его будет невозможно. - person John Rasch; 14.04.2009
comment
Спасибо за вашу помощь, Джон. Я написал базовую функцию отступа, которая, надеюсь, решит мою проблему (собираюсь опубликовать ее как ответ, если вы хотите взглянуть). - person Josh Leitzel; 14.04.2009

Какой метод вы вызываете при печати xml?

Я использую это:

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

(...)

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

Он работает отлично, но печатает только элемент, поэтому вы должны распечатать часть <?xml ... ?> вручную.

person Jindra    schedule 20.04.2012

Большинство ответов в этой теме касаются потока текста xml. Вот еще один подход, использующий функциональные возможности dom для выполнения задания отступа. Метод dom loadXML() импортирует символы отступа, присутствующие в источнике xml, в виде текстовых узлов. Идея состоит в том, чтобы удалить такие текстовые узлы из dom, а затем воссоздать их в правильном формате (подробнее см. комментарии в коде ниже).

Функция xmlIndent() реализована как метод класса indentDomDocument, унаследованного от domDocument. Ниже приведен полный пример того, как его использовать:

$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
person Community    schedule 15.08.2013

Йо выглядывает,

только что узнал, что, по-видимому, корневой элемент XML может не содержать текстовых дочерних элементов. Это неинтуитивно а. ф. Но, видимо, именно поэтому, например,

$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

Так вот, х. т. час и Т. Д.

person blaaaaaaaaaaa    schedule 23.07.2017

header("Content-Type: text/xml");

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

Если вы используете какое-либо расширение, отличное от .xml, сначала установите правильное значение для заголовка Content-Type.

person Community    schedule 23.08.2009