在 PHP 中生成 XML 文档(转义字符)
Posted
技术标签:
【中文标题】在 PHP 中生成 XML 文档(转义字符)【英文标题】:Generating XML document in PHP (escape characters) 【发布时间】:2011-04-26 19:42:24 【问题描述】:我正在从 php 脚本生成 XML 文档,我需要转义 XML 特殊字符。 我知道应该转义的字符列表;但是正确的方法是什么?
字符应该只用反斜杠(\')转义还是正确的方法是什么? 是否有任何内置的 PHP 函数可以为我处理这个问题?
【问题讨论】:
@Tchalvak:你在赏金描述中批评的许多观点都是错误的。我试图通过现有答案使其可见,希望这会有所帮助。 我没有在回答中建议将 DOM API 仅用于字符串转义。我建议您使用该 API 生成整个 XML 文档。这是对您在赏金描述中提到的问题的回应。 【参考方案1】:我创建了一个简单的函数,它使用 XML 中的 five "predefined entities" 进行转义:
function xml_entities($string)
return strtr(
$string,
array(
"<" => "<",
">" => ">",
'"' => """,
"'" => "'",
"&" => "&",
)
);
使用示例Demo:
$text = "Test & <b> and encode </b> :)";
echo xml_entities($text);
输出:
Test &amp; <b> and encode </b> :)
使用str_replace
也可以达到类似的效果,但是由于双重替换,它很脆弱(未经测试,不推荐):
function xml_entities($string)
return str_replace(
array("&", "<", ">", '"', "'"),
array("&", "<", ">", """, "'"),
$string
);
【讨论】:
您需要将"&"
和"&amp;"
数组元素移到开头,否则创建的所有其他实体也将被替换。此外,strtr
解决方案似乎根本不起作用。
对于 OP 回答他自己的问题,这里确实存在严重的缺陷。我试图编辑它,但我想知道是否使用了原始代码;) - @Nicky De Maeyer:是的,XML 中有五个预定义的实体,我放置了一个链接。
是的,但是属性转义不同于元素转义(即您放入属性中的内容)。 See my Java code for an example。你不想做@TomasJancik 正在做的事情。 请不要!。使用图书馆或做正确的事。【参考方案2】:
使用DOM 类来生成整个 XML 文档。它将处理我们甚至不想关心的编码和解码。
编辑:这被@Tchalvak批评了:
DOM 对象创建了一个完整的 XML 文档,它不容易自己编码一个字符串。
这是错误的,DOMDocument 可以正确输出只是一个片段而不是整个文档:
$doc->saveXML($fragment);
给出:
Test & <b> and encode </b> :)
Test &amp; <b> and encode </b> :)
如:
$doc = new DOMDocument();
$fragment = $doc->createDocumentFragment();
// adding XML verbatim:
$xml = "Test & <b> and encode </b> :)\n";
$fragment->appendXML($xml);
// adding text:
$text = $xml;
$fragment->appendChild($doc->createTextNode($text));
// output the result
echo $doc->saveXML($fragment);
见Demo
【讨论】:
根据bugs.php.net/bug.php?id=31191,您特别想使用createTextNode 函数以获得正确的自动转义。 我认为@Tchalvak 的问题在于它不是基于流的。那就是使用 DOM 会创建一堆对象。正如我在answer 中提到的那样,他可以使用 XMLWriter 或者只是将我的 Java 代码移植到 PHP 以进行正确的转义(Tomas Jancik 的方式不正确)。 哎呀,eval.in/10980 是使用本机处理的xmlentities()
函数的正确实现。可能有一些开销,但是,使用本机/面向未来的功能可能值得高枕无忧。
呃,它使用 dom 对象似乎无法处理双引号或单引号:eval.in/11043
@Pete:我最终使用了您的单线解决方案。它工作得很好,除了它对诸如 å=>å 之类的东西进行编码。和 ø=>ø即使它们可以安全地保持未编码状态。为了解决这个问题,我必须指定字符集,并使用 saveXml 而不是 savehtml。 new DOMDocument('1.0', 'utf-8'))->appendChild(new DOMText($value))->parentNode->saveXml()
。 PS:我认为您的评论可能应该是它自己的答案。【参考方案3】:
htmlspecialchars()
函数呢?
htmlspecialchars($input, ENT_QUOTES | ENT_XML1, $encoding);
注意:ENT_XML1
标志仅在您拥有 PHP 5.4.0 或更高版本时可用。
htmlspecialchars()
用这些参数替换以下字符:
&amp;
(和号)变为&amp;
"
(双引号)变为&quot;
'
(单引号)变为&apos;
&lt;
(小于)变为&lt;
&gt;
(大于)变为&gt;
您可以使用get_html_translation_table()
函数获取翻译表。
【讨论】:
对于 XML 兼容编码,没有特别需要使用ENT_XML1
- 至少对于 PHP 版本 4.3.0 到 5.5.0alpha4。如果您可以使用数字而不是命名实体,那么简单的 htmlspecialchars($input, ENT_QUOTES, $encoding);
也可以完成这项工作。【参考方案4】:
努力处理XML实体问题,这样解决:
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
【讨论】:
只有当这些实体在 XML 中定义时才有效,请参阅w3.org/TR/xml-entity-names【参考方案5】:为了获得一个有效的最终 XML 文本,您需要转义所有 XML 实体,并以与 XML 文档处理指令所述相同的编码编写文本(<?xml
行中的“编码”) .重音字符不需要转义,只要编码为文档即可。
但是,在许多情况下,简单地用htmlspecialchars
转义输入可能会导致实体被双重编码(例如&amp;eacute;
会变成&amp;eacute;
),所以我建议先解码html 实体:
function xml_escape($s)
$s = html_entity_decode($s, ENT_QUOTES, 'UTF-8');
$s = htmlspecialchars($s, ENT_QUOTES, 'UTF-8', false);
return $s;
现在您需要确保所有重音字符在 XML 文档编码中都是有效的。我强烈建议始终以 UTF-8 编码 XML 输出,因为并非所有 XML 解析器都尊重 XML 文档处理指令编码。如果您的输入可能来自不同的字符集,请尝试使用utf8_encode()
。
有一种特殊情况,您的输入可能来自以下编码之一:ISO-8859-1、ISO-8859-15、UTF-8、cp866、cp1251、cp1252 和 KOI8-R——PHP 处理它们都一样,但它们之间有一些细微的差别——其中一些甚至iconv()
都无法处理。我只能通过补充utf8_encode()
行为来解决这个编码问题:
function encode_utf8($s)
$cp1252_map = array(
"\xc2\x80" => "\xe2\x82\xac",
"\xc2\x82" => "\xe2\x80\x9a",
"\xc2\x83" => "\xc6\x92",
"\xc2\x84" => "\xe2\x80\x9e",
"\xc2\x85" => "\xe2\x80\xa6",
"\xc2\x86" => "\xe2\x80\xa0",
"\xc2\x87" => "\xe2\x80\xa1",
"\xc2\x88" => "\xcb\x86",
"\xc2\x89" => "\xe2\x80\xb0",
"\xc2\x8a" => "\xc5\xa0",
"\xc2\x8b" => "\xe2\x80\xb9",
"\xc2\x8c" => "\xc5\x92",
"\xc2\x8e" => "\xc5\xbd",
"\xc2\x91" => "\xe2\x80\x98",
"\xc2\x92" => "\xe2\x80\x99",
"\xc2\x93" => "\xe2\x80\x9c",
"\xc2\x94" => "\xe2\x80\x9d",
"\xc2\x95" => "\xe2\x80\xa2",
"\xc2\x96" => "\xe2\x80\x93",
"\xc2\x97" => "\xe2\x80\x94",
"\xc2\x98" => "\xcb\x9c",
"\xc2\x99" => "\xe2\x84\xa2",
"\xc2\x9a" => "\xc5\xa1",
"\xc2\x9b" => "\xe2\x80\xba",
"\xc2\x9c" => "\xc5\x93",
"\xc2\x9e" => "\xc5\xbe",
"\xc2\x9f" => "\xc5\xb8"
);
$s=strtr(utf8_encode($s), $cp1252_map);
return $s;
【讨论】:
【参考方案6】:如果你需要正确的 xml 输出,simplexml 是最好的选择:
http://www.php.net/manual/en/simplexmlelement.asxml.php
【讨论】:
【参考方案7】:正确的转义是获得正确 XML 输出的方法,但您需要为 属性 和 元素 处理 不同 的转义。 (那是托马斯的回答不正确)。
不久前我写/偷了一些Java code 来区分属性和元素转义。原因是 XML 解析器认为所有空白都是特殊的,尤其是在属性中。
将它移植到 PHP 应该很简单(您可以使用 Tomas Jancik 的方法和上述适当的转义)。如果您使用UTF-8
,则不必担心转义扩展实体。
如果你不想移植我的 Java 代码,你可以查看 XMLWriter,它是基于流的并使用 libxml,所以它应该非常高效。
【讨论】:
+1 因为我不知道 XMLWriter 会自动为您完成这项工作。【参考方案8】:您可以使用以下方法: http://php.net/manual/en/function.htmlentities.php
这样所有实体 (html/xml) 都会被转义,您可以将字符串放在 XML 标记中
【讨论】:
这是一个糟糕的解决方案,因为 HTML 实体是比 XML 实体更大的集合,并且大多数 XML 解析器无法识别许多不在 XML 实体列表中的 HTML 实体。【参考方案9】:基于 sadeghj 的解决方案,以下代码对我有用:
/**
* @param $arr1 the single string that shall be masked
* @return the resulting string with the masked characters
*/
function replace_char($arr1)
if (strpos ($arr1,'&')!== FALSE) //test if the character appears
$arr1=preg_replace('/&/','&', $arr1); // do this first
// just encode the
if (strpos ($arr1,'>')!== FALSE)
$arr1=preg_replace('/>/','>', $arr1);
if (strpos ($arr1,'<')!== FALSE)
$arr1=preg_replace('/</','<', $arr1);
if (strpos ($arr1,'"')!== FALSE)
$arr1=preg_replace('/"/','"', $arr1);
if (strpos ($arr1,'\'')!== FALSE)
$arr1=preg_replace('/\'/',''', $arr1);
return $arr1;
【讨论】:
【参考方案10】: function replace_char($arr1)
$arr[]=preg_replace('>','>', $arr1);
$arr[]=preg_replace('<','<', $arr1);
$arr[]=preg_replace('"','"', $arr1);
$arr[]=preg_replace('\'','&apos', $arr1);
$arr[]=preg_replace('&','&', $arr1);
return $arr;
【讨论】:
这在很多方面都很糟糕:(1) 无需使用正则表达式进行愚蠢的搜索和替换。 (2) 替换值不是正确的实体(它们不以分号结尾)。 (3) 您将分别获得每个替换版本的数组。 (4) 这种策略甚至不是面向未来的;或者您每次规格更改时都要维护它?我不知道这是怎么得到 4 票的。以上是关于在 PHP 中生成 XML 文档(转义字符)的主要内容,如果未能解决你的问题,请参考以下文章