使用XMLWriter将变量数据输出为CDATA XML

3

我正在尝试使用PHP创建一个Web服务,供应用程序与之通信,该服务将从数据库获取数据并将其放入XML格式以供应用程序使用。但是,其中一列包含HTML代码,需要(我认为)作为CDATA进行输出。然而,我无法成功实现这一点。请给予建议。

<?php
mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
mysql_select_db(DB_NAME);

$sql = "SELECT post_date_gmt, post_content, post_title FROM [schema].wp_posts WHERE post_status = \"publish\" && post_type = \"post\" ORDER BY post_date_gmt DESC;";
$res = mysql_query($sql);

$xml = new XMLWriter();

$xml->openURI("php://output");
$xml->startDocument();
$xml->setIndent(true);

$xml->startElement('BlogPosts');

while ($row = mysql_fetch_assoc($res)) {

    $xml->startElement("Post");

    $xml->startElement("PostDate");
    $xml->writeRaw($row['post_date_gmt']);
    $xml->endElement();

    $xml->startElement("PostTitle");
    $xml->$writeRaw($row['post_title']);
    $xml->endElement();

    $xml->startCData("PostContent");
    $xml->writeCData($row['post_content']);
    $xml->endCData();

    $xml->endElement();

}

$xml->endElement();

header('Content-type: text/xml');
$xml->flush();

?>

非常感谢您提供的任何帮助!


$xml->$writeRaw - 第二个 "$" 很可能是错误的? - hakre
我必须诚实地告诉你,自上周四以来,我一直在尝试使用JSON编码,但是在创建它时遇到了极大的困难,而在尝试解析它时又遇到了同样的问题。我觉得XML更加舒适,所以我回到了它,直到我能够掌握NSJSONSerializer并编写代码以生成无警告的JSON。 - Kirkland
3个回答

5
不要使用 XMLWriter::writeRaw(),除非您真的想直接编写XML片段。 "Raw" 表示库中不会进行任何转义。
正确的将文本写入XML文档的方法是使用 XMLWriter::text()
$xml->startElement('PostTitle');
$xml->text('foo & bar');
$xml->endElement();

输出:

<?xml version="1.0"?>
<PostTitle>foo &amp; bar</PostTitle>

如果在此示例中使用XMLWriter :: writeRaw(),结果将包含未转义的&并且无效XML。

CDATA节是字符节点,类似于文本节点,但允许特殊字符而不需要转义,并保留空格。您始终必须单独创建元素节点。元素节点可以包含多个其他节点,甚至多个CDATA节。

XmlReader有两种创建CDATA节的方法:

一种单独的方法:

$xml->startElement("PostContent");
$xml->writeCData('<b>post</b> content');
$xml->endElement();

输出:

<?xml version="1.0"?>
<PostContent><![CDATA[<b>post</b> content]]></PostContent>

或者使用开始/结束方法:

$xml->startElement("PostContent");
$xml->startCData();
$xml->text('<b>post</b> content');
$xml->text(' more content');
$xml->endCData();
$xml->endElement();

输出:

<?xml version="1.0"?>
<PostContent><![CDATA[<b>post</b> content more content]]></PostContent>

非常感谢您的回复!我已经添加了您的更改,但是当它到达$xml->text($row=['post_title']);时,出现了错误。该段新代码为:$xml->startElement("PostTitle"); $xml->$text($row['post_title']); $xml->endElement();使用相同的代码完美地打印日期,所以我不确定这里出了什么问题。您能否帮我解决这最后一点问题? - Kirkland
好的,我复制并粘贴了工作段落,现在它可以工作了,但只有有时候。由于某种原因,它只有部分时间开始、填充和结束post_title元素部分。查询中的该列始终被填充,所以我仍然不知道发生了什么。 - Kirkland
1
$xml->$text($row['post_title']); 多了一个 $ 符号。正确的写法应该是 $xml->text($row['post_title']); - ThW
ThW,他们需要一个“超赞”按钮的标志,以感谢您的帮助!我还有一个关于这个问题的问题,但是由于它与原始帖子无关,所以我将其作为新问题提出。如果您愿意协助我,我会非常感激。您应该很快就能在我的个人资料中看到它。 - Kirkland

0

您可以将其添加到需要使用CDATA包装的元素中,如下所示:

 $xml->writeRaw('<![CDATA['.$row['post_date_gmt'].']]>');

1
这可能会输出无效的XML - 例如,在CDATA部分中仍需要转义& - ThW
为什么你需要转义 & 字符呢?如果我不转义它测试这段代码,它仍然可以工作。 - Ole Haugset
1
如果$row['post_date_gmt'](可能不是但也可能是,这就是问题所在,因为它可能代表任何变量数据)中包含"]]>",那么这就是明显的错误。此外,这并不是很聪明:使用XMLWriter并假设问题尚未解决将使使用XMLWriter变得多余。这对于提问者来说也是某种程度上的退化。正确的答案应该是:$xml->writeCData($row['post_date_gmt']); - 因为它已经包装好了。没有必要重新发明轮子。 - hakre
1
我错了,抱歉。你不能在CDATA中使用转义字符。就像hakre所指出的那样, ]]> 可能会破坏XML。在这种情况下,DOM会分割CDATA部分。 - ThW

0

ThW的回答总体来说很周到,也是正确的。它很好地解释了PHP中XMLWriter接口的使用方法。

他也应该得到一部分功劳,因为我们昨天在聊天中讨论了这个问题。

然而,在XML中使用CDATA也存在一些限制,这也适用于使用XMLWriter进行CDATA的两种方式:

字符串“]]>”不能放置在CDATA节中,因此不允许嵌套CDATA节(格式正确性约束)。

来源:CDATA Section - 比较2.7 CDATA Sections

通常,XMLWriter接受未编码用途的字符串数据。例如,如果您传递一些文本,它将被正确编码写入(除非使用了XMLWriter::writeRaw)。

但是,如果您开始一个CDATA部分,然后写入文本或者您直接写入CDATA,则传递的字符串不能以及包含另一个CDATA部分。这意味着它不能包含字符序列“]]>”,因为这会过早地结束CDATA部分。

因此,将有效数据传递给XMLWriter的责任仍由这些方法的用户承担。

通常很容易做到这一点(单个八位字节,基于US-ASCII字符集的二进制编码和UTF-8 Unicode),下面是一些示例代码:

/**
 * prepare text for CDATA section to prevent invalid or nested CDATA
 *
 * @param $string
 *
 * @return string
 * @link http://www.w3.org/TR/REC-xml/#sec-cdata-sect
 */
function xmlwriter_prepare_cdata_text($string) {
    return str_replace(']]>', ']]]]><![CDATA[>', (string) $string);
}

并附带一个使用示例:

$xml = new XMLWriter();
$xml->openURI("php://output");
$xml->startDocument();

$xml->startElement("PostContent");
$xml->writeCDATA(xmlwriter_prepare_cdata_text('<![CDATA[Foo & Bar]]>'));
$xml->endElement();

$xml->endElement();

优秀的输出:

<?xml version="1.0"?>
<PostContent><![CDATA[<![CDATA[Foo & Bar]]]]><![CDATA[>]]></PostContent>

DOMDocument 在幕后已经执行了非常类似的操作:

$dom = new DOMDocument();
$dom->appendChild(
    $dom->createElement('PostContent')
);
$dom->documentElement->appendChild(
    $dom->createCdataSection('<![CDATA[Foo & Bar]]>')
);
$dom->save("php://output");

输出:

<?xml version="1.0"?>
<PostContent><![CDATA[<![CDATA[Foo & Bar]]]]><![CDATA[>]]></PostContent>

为了从技术上理解PHP中的XMLWriter为什么会有这种行为,您需要知道XMLWriter基于libxml2库。PHP中的扩展大部分工作都是将调用传递给libxml:
PHP的xmlwriter_write_cdata委派给libxml的xmlTextWriterWriteCDATA,其执行怀疑序列xmlTextWriterStartCDATA、xmlTextWriterWriteString和xmlTextWriterEndCDATA。

xmlTextWriterWriteString 在许多例程中使用(例如写入 PI),但仅对于某些文本写入情况,内容参数字符串编码:

  • Name,
  • Text 和
  • Attribute。

对于所有其他情况,它会按原样传递。这包括 CDATA,因此传递给 XMLWriter::writeCData 的数据必须符合 XML CData 的要求(因为该方法会写入它):

  • [20] CData ::= (Char* - (Char* ']]>' Char*))

从技术上讲,这意味着:任何不包含“]]>”的字符串。

这很容易被忽视,我自己昨天也怀疑这可能是一个错误。而且我不是唯一一个,PHP.net 上的相关错误报告是:https://bugs.php.net/bug.php?id=44619,已经有好几年了。

同时请参阅XML中的<![CDATA[]]>是什么意思?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接