如何删除XML字段前后的所有空格字符?
<data version="2.0">
<field>
1
</field>
<field something=" some attribute here... ">
2
</field>
</data>
注意1和2之前的空格和这里的'some attribute…',我想用PHP删除它。
if(($xml = simplexml_load_file($file)) === false) die();
print_r($xml);
数据似乎不是字符串,我需要在每个变量之前附加(string)。为什么?
您可能想使用这样的内容:
$str = file_get_contents($file);
$str = preg_replace('~s*(<([^>]*)>[^<]*</2>|<[^>]*>)s*~','$1',$str);
$xml = simplexml_load_string($xml,'SimpleXMLElement', LIBXML_NOCDATA);
我还没有试过,但是你可以在http://www.lonhosford.com/lonblog/2011/01/07/php-simplexml-load-xml-file-preserve-cdata-remove-whitespace-between-nodes-and-return-json/上找到更多。
注意,左括号和右括号之间的空格(<x> _space_ </x>
)和属性之间的空格(<x attr=" _space_ ">
)实际上是XML文档数据的一部分(与<x> _space_ <y>
之间的空格相反),因此我建议您使用的源代码应该少一些空格。
要在PHP中做到这一点,您首先必须将文档转换为DOMDocument,以便您可以通过DOMXPath寻址您想要规范化空白的节点。(xpath in) SimpleXMLElement访问文本节点的限制太大,无法满足此操作的需要。
访问叶子元素和所有属性中的所有文本节点的xpath查询是:
//*[not(*)]/text() | //@*
考虑到$xml
是SimpleXMLElement,您可以像以下示例一样进行空白归一化:
$doc = dom_import_simplexml($xml)->ownerDocument;
$xpath = new DOMXPath($doc);
foreach ($xpath->query('//*[not(*)]/text()|//@*') as $node) {
/** @var $node DOMText|DOMAttr */
$node->nodeValue = trim(preg_replace('~s+~u', ' ', $node->nodeValue), ' ');
}
您也许可以将其扩展到所有文本节点(如相关问题A中建议的那样),但在特定情况下,这可能需要文档规范化。由于Xpath中的text()
在文本节点和Cdata-sections之间没有区别,因此您可能希望跳过这些类型的节点(DOMCdataSection),或者在加载文档时将它们扩展为文本节点(为此使用LIBXML_NOCDATA
选项),以获得更有用的结果。
数据似乎不是字符串,我需要在每个变量之前附加(string)。为什么?
因为它是一个类型为SimpleXMLElement的对象,如果您想要这样一个对象(元素)的字符串值,您需要将其强制转换为string。参见下面的参考问题:
- 将SimpleXML对象强制为字符串,而不考虑上下文
最后但并非最不重要的是:当您在SimpleXMLElement上使用print_r
或var_dump
时,不要信任它:它没有显示真相。例如,您可以覆盖__toString()
,这也可以解决您的问题:
class TrimXMLElement extends SimpleXMLElement
{
public function __toString()
{
return trim(preg_replace('~s+~u', ' ', parent::__toString()), ' ');
}
}
$xml = simplexml_load_string($buffer, 'TrimXMLElement');
print_r($xml);
即使转换为string通常会应用(例如echo
), print_r
的输出仍然不会反映这些变化。所以最好不要依赖它,它永远不能反映全貌。
这个答案的完整示例代码(在线演示):
<?php
/**
* Remove starting and ending spaces from XML elements
*
* @link https://stackoverflow.com/a/31793566/367456
*/
$buffer = <<<XML
<data version="2.0">
<field>
1
</field>
<field something=" some attribute here... ">
2 <![CDATA[ 34 ]]>
</field>
</data>
XML;
class TrimXMLElement extends SimpleXMLElement implements JsonSerializable
{
public function __toString()
{
return trim(preg_replace('~s+~u', ' ', parent::__toString()), ' ');
}
function jsonSerialize()
{
$array = (array) $this;
array_walk_recursive($array, function(&$value) {
if (is_string($value)) {
$value = trim(preg_replace('~s+~u', ' ', $value), ' ');
}
});
return $array;
}
}
$xml = simplexml_load_string($buffer, 'TrimXMLElement', LIBXML_NOCDATA);
print_r($xml);
echo json_encode($xml);
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
$doc = dom_import_simplexml($xml)->ownerDocument;
$doc->normalizeDocument();
$doc->normalize();
$xpath = new DOMXPath($doc);
foreach ($xpath->query('//*[not(*)]/text()|//@*') as $node) {
/** @var $node DOMText|DOMAttr|DOMCdataSection */
if ($node instanceof DOMCdataSection) {
continue;
}
$node->nodeValue = trim(preg_replace('~s+~u', ' ', $node->nodeValue), ' ');
}
echo $xml->asXML();
由于simplexml_load_file()
将数据读取到数组中,因此您可以这样做:
function TrimArray($input){
if (!is_array($input))
return trim($input);
return array_map('TrimArray', $input);
}