PHP Conference Japan 2024

DOMDocument::saveXML

(PHP 5、PHP 7、PHP 8)

DOMDocument::saveXML 將內部 XML 樹狀結構轉儲回字串

說明

public DOMDocument::saveXML(?DOMNode $node = null, int $options = 0): string|false

從 DOM 表示法建立 XML 文件。此函式通常在從頭開始建立新的 dom 文件後呼叫,如下例所示。

參數

node

使用此參數只輸出特定的節點,而不是整個文件,且不包含 XML 宣告。

options

其他選項。支援 LIBXML_NOEMPTYTAGLIBXML_NOXMLDECL 選項。在 PHP 8.3.0 之前的版本,僅支援 LIBXML_NOEMPTYTAG 選項。

回傳值

回傳 XML,如果發生錯誤則回傳 false

錯誤/例外

DOM_WRONG_DOCUMENT_ERR

如果 node 來自另一個文件,則會引發此錯誤。

變更紀錄

版本 說明
8.3.0 現在支援 LIBXML_NOXMLDECL

範例

範例 1:將 DOM 樹狀結構儲存到字串

<?php

$doc
= new DOMDocument('1.0');
// 我們想要美觀的輸出
$doc->formatOutput = true;

$root = $doc->createElement('book');
$root = $doc->appendChild($root);

$title = $doc->createElement('title');
$title = $root->appendChild($title);

$text = $doc->createTextNode('This is the title');
$text = $title->appendChild($text);

echo
"儲存整個文件:\n";
echo
$doc->saveXML() . "\n";

echo
"只儲存標題部分:\n";
echo
$doc->saveXML($title);

?>

以上範例將輸出

Saving all the document:
<?xml version="1.0"?>
<book>
  <title>This is the title</title>
</book>

Saving only the title part:
<title>This is the title</title>

另請參閱

新增筆記

使用者貢獻筆記 16 筆記

11
liumenglei0211 at 163 dot com
5 年前
當節點內容為空時,如何顯示兩個標籤。

<?php
$dom
= new \DOMDocument('1.0');
$document = $dom->createElement('document');
$document = $dom->appendChild($document);
$head = $dom->createElement('title','this is title');
$content = $dom->createElement('content','');
$document->appendChild($head);
$document->appendChild($content);
echo
$dom->saveXML();
?>

在 XML 中,它們被認為是完全相同的事情,任何剖析器都應該識別這兩種形式。但是,如果您仍然需要,可以這樣寫

<?php
echo $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);
?>

範例 1:

<?xml version="1.0"?>
<document>
<title>this is title</title>
<content/>
</document>

範例 2:
<document>
<title>this is test</title>
<content></content>
</document>
16
devin at SPAMISBAD dot tritarget dot com
18 年前
花了一些時間搜尋才找出這個問題。到目前為止,我沒在手冊中看到太多關於這個小問題的解釋。(對於 PHP5,我相信)

當 DOM 的來源來自透過 load() 從檔案載入時,formatOutput = true; 似乎會失敗。例如

<?php
$dom
= new DOMDocument();
$dom->load ("test.xml");
$dom->formatOutput = true;

$new_tag = $dom->createElement ('testNode');
$new_tag->appendChild (
$dom->createElement ('test', 'this is a test'));
$dom->documentElement->appendChild ($new_tag);

printf ("<pre>%s</pre>", htmlentities ($dom->saveXML()));
?>

不會縮排輸出,並會將修改過的節點全部顯示在一長行中。當儲存到檔案時,會使編輯 config.xml 有點困難。

在 load() 之前加入 preserveWhiteSpace = false;,formatOutput 就能如預期運作。例如

<?php
$dom
= new DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->load ("test.xml");
$dom->formatOutput = true;

$new_tag = $dom->createElement ('testNode');
$new_tag->appendChild (
$dom->createElement ('test', 'this is a test'));
$dom->documentElement->appendChild ($new_tag);

printf ("<pre>%s</pre>", htmlentities ($dom->saveXML()));
?>

注意:如果你的載入的 XML 檔案 (test.xml) 有一個空的根節點,且該節點沒有被縮短或沒有子節點,這將無法運作。

範例

無法運作
<?xml version="1.0"?>
<root>
</root>

可以運作
<?xml version="1.0"?>
<root/>

可以運作
<?xml version="1.0"?>
<root>
<!-- 註解 -->
</root>

可以運作
<?xml version="1.0"?>
<root>
<child/>
</root>
16
vikramvmalhotra at hotmail dot com
15 年前
如果你在 XML 中儲存多位元組字元,那麼使用 saveXML() 儲存 XML 會產生問題。它會吐出轉換為編碼格式的字元。

<?php
$str
= domdoc->saveXML(); // 產生 "&x#1245;" 一些編碼過的資料
?>

請改用以下方式

<?php
$str
= domdoc->saveXML(domdoc->documentElement); // 產生 "保存しました" 正確的多位元組資料
?>
7
padys at tlen dot pl
20 年前
當你儲存整個文件時
DOMDocument->saveXML() 會產生在 DOMDocument->encoding 屬性中定義的編碼字串。

當你只儲存一個節點時
DOMDocument->saveXML(DOMNode) 總是會產生 UTF-8 編碼的字串。
5
mswiercz at mwerk dot com
20 年前
在使用 DOM 產生文件時,最小化記憶體的快速提示。

不要使用
$xmlStr = DOMDocument->saveXML();
echo $xmlStr;

來將大型 DOM 轉儲到輸出緩衝區,請使用 PHP 輸出流,如下所示

DOMDocument->save('php://output');

當產生大型 DOM 時,將會節省大量記憶體。
3
qjerry.com at gmail.com
15 年前
從輸出文件中移除 XML 宣告 (<?xml version="1.0" ... ?>) 最簡單(可能也是最快)的方法是,分別輸出 DOMDocument 的子節點

<?php

$document
= new DOMDocument();
$document->load('/some/file.xml');

// 這也會輸出頂層的 doctype 和註解
foreach($document->childNodes as $node)
$result .= $document->saveXML($node)."\n";

echo
$result;

?>

當處理瀏覽器相容性問題時,這可能很有用,例如,IE6 中使用有效的 XHTML 時會出現眾所周知的問題。
4
JITR
17 年前
針對 `devin at SPAMISBAD dot tritarget dot com'' 的貼文的回覆

感謝您指出 `formatOutput' 與 `load*()' 的陷阱。這確實讓我避免了一些可能發生的意外。

我認為這種看似奇怪的行為是可以解釋的。警告:以下內容主要基於推論和實驗。較少基於研究來源和規格(我不確定其中一些是否會提供答案,至少不容易)。

正如您所指出的,必須在從原始字串載入 DOM 之前設定 `preserveWhiteSpace' (我正在使用 `loadXML()',但我相信您使用的 `load()' 情況應該相同)。這看起來很合理,因為此屬性似乎控制解析和 DOM 建立過程,在此過程中,包含空白的文字節點會被包含或丟棄。這可以透過轉儲 DOM 結構並比較基於 `preserveWhiteSpace' 值的結果來證明。將 `preserveWhiteSpace' 設定為 `FALSE' 時,傳回的 DOM 中將不存在包含空白的文字節點。當此屬性為 `TRUE' 時,這些節點將會存在。

注意:在上一段中談論空白時,如果我沒弄錯的話,我們肯定是在談論所謂的「元素內容中的空白」或「元素內容空白」。另請參閱我在 `DOMText->isWhitespaceInElementContent()' 方法註釋中的評論。

至於 `saveXLM()' 輸出的神秘影響,我認為解釋在於上述空白文字節點的存在與否。這也透過實驗證明:在將此類節點新增到不包含任何節點的 DOM 中(該 DOM 是使用 `preserveWhiteSpace' 設定為 `FALSE' 的 `loadXML()' 建立的)之後,輸出格式化受到影響,以至於在新增節點後,文件的其餘部分遺失了格式化。我認為空白文字節點的存在會強制進行此類渲染,這些節點的內容會用於分隔相鄰的節點,從而停用預設格式化。只有在不存在此類文字節點時,輸出格式化才會生效(當然,前提是 `formatOutput' 設定為 `TRUE')。

嗯,我不太了解的是,您是如何在 `formatOutput' 設定為 `TRUE' 的情況下獲得單行輸出的。當沒有空白文字節點存在時(也就是說,使用 `preserveWhiteSpace' 設定為 `FALSE' 載入 XML 時)*且* `formatOutput' 設定為 *`FALSE'* 時,我就發生過這種情況(如果 `formatOutput' 的值相反,則格式化應會執行其工作,而且您不應該只得到一行)。但我沒有看過您的來源。也許您的 DOM 中有不包含換行符號的空白節點?

至於關於根元素的注意事項,我沒有看到空根元素在縮短或完整形式中出現任何問題。當您說它「可以運作」或「無法運作」時,您的想法是什麼?
4
xianrenb at gmail dot com
12 年前
如果您想儲存 xhtml (在字串中),您可以嘗試以下方法
<?php
$doc
= new DOMDocument('1.0');

// ...

$xhtml = (string) $doc->saveXML($doc->doctype);
$xhtml .= "\n";
$xhtml .= (string) $doc->saveXML($doc->documentElement);
?>
3
Alexander Fedulin
10 年前
正如 @fbernodi 先前所說,當您有多個定義的命名空間時,DOMNode 的 saveXML 會出現問題。這個簡單的解決方案是

<code>
// $node 是其他文件中某個節點
$temp_document = new DOMDocument('1.0', 'utf-8');
$temp_document->appendChild($temp_document->importNode($node, true));
echo $temp_document->saveXML();
</code>
1
fbernoldi at gmail dot com
11 年前
您好,

我的一些解析程式碼中出現了意料之外的行為,因為這個 saveXML 方法在儲存標籤時沒有指定命名空間。如果您嘗試將從 DOMNode 儲存的字串載入到 DOMDocument 中,而該 DOMNode 具有參考的命名空間,但它在原始文件中未在該元素中定義,則您會得到一個格式不正確的 xml。在範例中,您可以看到這個問題和可能的解決方案,另一個解決方案可能是將命名空間參考新增到 ChildElement 節點 (<testNS:ChildElement testNS="http://example.org">),但在我的情況下,並不需要這樣做。

<?php

// 測試 XML
$test_xml =
"<?xml version=\"1.0\"?>
<testNS:RootNode xmlns:testNS=\"http://example.org\">
<testNS:ChildElement>
<testNS:AnotherChildElement>I'm a Another Child node.</testNS:AnotherChildElement>
</testNS:ChildElement>
</testNS:RootNode>"
;

// 我們定義測試用的 DOMDocument
$domDoc = new DOMDocument("1.0");
$domDoc->loadXML($test_xml);

// 我們使用 xpath 搜尋 ChildElement:
$domXPath = new DOMXPath($domDoc);
$domXPath->registerNamespace("testNS", "http://example.org");
$DOMNodeList_ChildElement = $domXPath->query("//testNS:RootNode/testNS:ChildElement");
$ChildElement = $DOMNodeList_ChildElement->item(0);

echo
"無法直接載入到另一個文件的 XML:\n\n";
echo
$domDoc->saveXML($ChildElement);

/* 此處輸出:
*
* 無法直接載入到另一個文件的 XML:
*
* <testNS:ChildElement>
* <testNS:AnotherChildElement>I'm a Another Child node.</testNS:AnotherChildElement>
* </testNS:ChildElement>
*
*/

/**
* 輔助函式,將元素複製到另一個文件。
* @param DOMElement $node 要複製的節點。
* @param DOMDocument $doc 要參考元素的目標文件。
* @return DOMElement 新複製的元素,不帶命名空間。
*/
function cloneNode($node, $doc) {
// 使用原始 localName (不帶命名空間) 建立新元素
$nd = $doc->createElement($node->localName);

// 複製屬性
foreach($node->attributes as $value)
$nd->setAttribute($value->localName, $value->value);

// 如果沒有子節點,則完成。
if (!$node->childNodes)
return
$nd;
// 如果有子節點,則加入它們
foreach($node->childNodes as $child) {
if (
$child->nodeName=="#text") // 僅用於複製文字節點,例如文字註解、空格、Tab 等。
$nd->appendChild($doc->createTextNode($child->nodeValue));
else
$nd->appendChild(cloneNode($child, $doc)); // 遞迴複製所有子節點。
}
return
$nd;
}

// 新文件,用於參考不帶命名空間的新節點
$domDoc2 = new DOMDocument("1.0");

// 我們複製這個節點,移除命名空間
$new_node = cloneNode($ChildElement, $domDoc2);

echo
"\n\n我們可以將此載入到 DOMDocument 中,不會有問題:\n\n";
echo
$domDoc2->saveXML($new_node);

/*
* 我們可以將此載入到 DOMDocument 中,不會有問題:
*
* <ChildElement>
* <AnotherChildElement>I'm a Another Child node.</AnotherChildElement>
* </ChildElement>
*/

?>
1
Kriogen
16 年前
在你的 php 檔案中,先用以下內容建立 test.xml
<?xml version="1.0" encoding="utf-8"?>
<Photos>
</Photos>

然後貼上以下程式碼

<?php
$simp
= simplexml_load_file('test.xml');
$node = $simp->addChild('home');
$node->addChild('mychild', 'insert text');
$s = simplexml_import_dom($simp);
$s->saveXML('test.xml');
?>

這段程式碼會在根節點中建立一個子節點。
擁有者 http://www.mensfashion.ru
2
samstah at gmail dot com
14 年前
我們發現使用 DOMDocument::saveHTML() 會轉換成 HTML 4.01 相容的標記,而不是 XHTML。簡單的解決方法是改用 saveXML(),儘管這會在頂端加入 XML 宣告。

對於 qjerry.com at gmail.com,感謝以下提供的指示 - 但我認為最簡單的方法似乎是使用
<?php $domDocument->saveXML($domDocument->documentElement); ?>

當然,如果您處理的是 XHTML,這也會移除文件中任何 <!DOCTYPE> 宣告。
1
shinsh at shinmugen dot net
16 年前
請注意,這個函式在 5.2.6 版本中已變更。加入一個並非必要的必要參數並不是最聰明的做法,尤其是對於一個經常使用的函式。

如果您有錯誤需要修正程式,請像這樣填寫第一個參數

$dom->saveXML($dom->documentElement);

為什麼開發人員不簡單地將其作為可選參數實作,並將預設參數修正為 documentElement?
1
Sander
17 年前
請注意,對於大型 DOM 樹(數萬個元素,至少嵌套數層),當您呼叫 saveXML() 時,將 formatOutput 設定為 true 會將記憶體使用量提高到非常誇張的程度。(使用 PHP 5.2.1 測試)。美觀的輸出不值得付出這樣的代價。
1
Anonymous
18 年前
我使用了 "joe" 發佈的函式,但以下方法對我來說可以取得 innerXML
<?php
$itemLeido
= $XMLRespuesta->getElementsByTagName("articulos");
foreach(
$itemLeido as $node) {
echo(
$node->ownerDocument->saveXML($node));
}
?>
2
nevyn at NOSPAM dot email dot PLEASE dot it
17 年前
一個小函式,用於取得 XML 節點的完整 XML 內容。

function innerXml($node)
{
$out = $node->ownerDocument->saveXML($node);
$re = "{^<(\\w*)(?:\\s*\\w+=(?:\"[^\"]*\"|\'[^\']*\'))*\\s*>(.*)</\\1>$}";
preg_match($re, $out, $mat);
return $mat[2];
}
To Top