PHP SimpleXML无法保留XML属性中的换行符

8
我需要解析外部提供的XML文件,其中包含具有换行符的属性。使用SimpleXML时,似乎会丢失这些换行符。根据另一个stackoverflow问题,虽然远非理想,但换行符应该是有效的XML格式。
为什么会丢失它们?[编辑] 我该如何保留它们? [/编辑]
这是一个演示文件脚本(请注意,当换行符不在属性中时,它们会被保留)。 带嵌入式XML的PHP文件
$xml = <<<XML
<?xml version="1.0" encoding="utf-8"?>
<Rows>
    <data Title='Data Title' Remarks='First line of the row.
Followed by the second line.
Even a third!' />
    <data Title='Full Title' Remarks='None really'>First line of the row.
Followed by the second line.
Even a third!</data>
</Rows>
XML;

$xml = new SimpleXMLElement( $xml );
print '<pre>'; print_r($xml); print '</pre>';

print_r输出结果

SimpleXMLElement Object
(
    [data] => Array
        (
            [0] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [Title] => Data Title
                            [Remarks] => First line of the row. Followed by the second line. Even a third!
                        )

                )

            [1] => First line of the row.
Followed by the second line.
Even a third!
        )

)

你应该在 PHP 主页上问这个问题。我猜是因为它是一个简单的 XML 解析器。 - jbasko
你能再解释一下你所说的PHP主页是什么意思吗? - Joshua
最初你的问题是“为什么SimpleXML会做它所做的事情?”这是你可以问它的开发者而不是用户。 - jbasko
谢谢你的建议,Zilupe。现在bobince已经回答了“SimpleXML为什么这样做?”我想我会把这个问题留在stackoverflow上,希望有人能够补充其他选项来保留换行符! - Joshua
6个回答

13

使用SimpleXML时,换行符似乎丢失了。

是的,这是预期的...事实上,任何符合规范的XML解析器都要求属性值中的换行符代表简单的空格。请参见XML规范中的attribute value normalisation

如果属性值中应该有一个真正的换行符,请在XML中包含&#10;字符引用,而不是原始换行符。


3
稍微澄清一下:换行符是有效的,但为了符合规范,XML解析器必须将它们缩减为单个空格字符(请参见bobince链接中的第3项)。 - TML
感谢您的链接Bobince,以及澄清TML。那么我现在的问题变成了,如何保留这些换行符?我从SharePoint Web服务接收到这些数据,因此无法更改XML以包括&#10。有没有一种方法可以在这方面覆盖解析器兼容性? - Joshua
很不幸,XML在这个问题上相当不灵活。如果Web服务在表示“ ”时产生了“\n”,那么这是一个错误(bug)。这个错误让人惊讶,因为任何XML序列化程序都应该正确处理这个基本功能。除非当然该服务正在使用正则表达式或字符串模板而不是使用正确的XML库! - bobince
除非您可以访问子类或猴子补丁您的XML解析器,否则您将无法更改它...而我认为SimpleXML使用libxml,您无法从PHP进行调整。预处理一般的XML输入以放置&#10;也有点行不通,因为您必须编写大部分XML解析器才能区分属性值中的换行符和直接在标记内部的换行符(其中&#10;是非法的)。像安东尼的这样的黑客技巧可能会起到临时修复的作用,如果目前确切的格式非常锁定。 - bobince
请仅返回翻译的文本:def factorial(n): if n == 0: return 1 else: return n * factorial(n-1)这是一个计算阶乘的递归函数。 - bobince

4

换行符的实体为&#10;。我尝试了你的代码,直到找到了一个可行的方法。但是这并不是非常优雅,我提前告诉你。

//First remove any indentations:
$xml = str_replace("     ","", $xml);
$xml = str_replace("\t","", $xml);

//Next replace unify all new-lines into unix LF:
$xml = str_replace("\r","\n", $xml);
$xml = str_replace("\n\n","\n", $xml);

//Next replace all new lines with the unicode:
$xml = str_replace("\n","&#10;", $xml);

Finally, replace any new line entities between >< with a new line:
$xml = str_replace(">&#10;<",">\n<", $xml);

基于你的示例,我们假设任何出现在节点或属性内部的新行都会在下一行有更多文本,而不是用<来打开一个新元素。

当然,如果下一行中有一些被包裹在行级元素中的文本,这种情况就会失败。


非常聪明!唯一的问题是我正在处理从SharePoint Web服务中喷出的大量SOAP封装的XML,所以这让我有点紧张去做这样的事情。不过根据bobince的帖子,看起来我可能必须朝这个方向走。我想知道是否有更优雅的方法来完成它。 - Joshua

1
假设 $xmlData 是您发送到解析器之前的 XML 字符串,这应该将属性中的所有换行符替换为正确的实体。我曾经遇到过来自 SQL Server 的 XML 问题。
$parts = explode("<", $xmlData); //split over <
array_shift($parts); //remove the blank array element
$newParts = array(); //create array for storing new parts
foreach($parts as $p)
{
    list($attr,$other) = explode(">", $p, 2); //get attribute data into $attr
    $attr = str_replace("\r\n", "&#10;", $attr); //do the replacement
    $newParts[] = $attr.">".$other; // put parts back together
}
$xmlData = "<".implode("<", $newParts); // put parts back together prefixing with <

可能可以用正则表达式更简单地完成,但这不是我的强项。


问题就在于换行符在 XML 属性中并不是合法的。然而,解析器往往会自己处理许多问题。无论如何,无效实体都应该进行编码。最好的解决方案是修复源代码,但如果没有源代码可用,这种方法也是可以接受的。 - Kevin Peno

1

下面的代码可以将该特定XML片段中的换行符替换为适当的字符引用。在解析之前运行此代码。

$replaceFunction = function ($matches) {
    return str_replace("\n", "&#10;", $matches[0]);
};
$xml = preg_replace_callback(
    "/<data Title='[^']+' Remarks='[^']+'/i",
    $replaceFunction, $xml);

0

嗯,这个问题很老了,但像我一样,有人最终可能会来到这个页面。

我有一个稍微不同的方法,我认为是这些方法中最优雅的。

在xml文件中,您可以放置一些唯一的单词,用于表示换行。

将xml更改为

<data Title='Data Title' Remarks='First line of the row. \n
Followed by the second line. \n
Even a third!' />

然后,当您在SimpleXML中获取所需节点的路径并以字符串输出时,请编写类似以下内容的代码:

$findme  = '\n';
$pos = strpos($output, $findme);
if($pos!=0)
{
$output = str_replace("\n","<br/>",$output);

它不一定要是 '\n',可以是任何唯一的字符。


0
这是对我有效的方法:
首先,将XML作为字符串获取:
    $xml = file_get_contents($urlXml);

然后进行替换:

    $xml = str_replace(".\xe2\x80\xa9<as:eol/>",".\n\n<as:eol/>",$xml);

"

这里使用了“.”和“< as:eol/ >”是因为我需要在那种情况下添加换行符。新的换行符“\n”可以替换成你喜欢的任何字符。

替换后,只需将xml字符串加载为SimpleXMLElement对象即可:

"
    $xmlo = new SimpleXMLElement( $xml );

Et Voilà


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