php simplexml
PHP版本5引入了SimpleXML,SimpleXML是一种用于读写XML的新应用程序编程接口(API)。 在SimpleXML中,表达式如下:
$doc->rss->channel->item->title
从文档中选择元素。 只要您对文档的结构有所了解,这些表达式就很容易编写。 但是,如果您不确切知道感兴趣的元素出现在何处(例如Docbook,HTML和类似的叙述性文档中的情况),则SimpleXML可以使用XPath表达式来查找元素。
从SimpleXML开始
假设您想要一个将RSS提要转换为HTMLPHP页面。 RSS是用于发布联合内容的基本XML格式。 该文档的根元素是rss
,其中包含单个channel
元素。 channel
元素包含有关提要的元数据,包括其标题,语言和URL。 它还包含包含在item
元素中的各种故事。 每个item
都有一个包含URL的link
元素以及一个包含纯文本的title
或description
(通常两者)。 不使用命名空间。 RSS不仅限于此,但这是本文需要了解的全部。 清单1显示了一个带有几个新闻项的典型示例。
清单1.一个RSS提要
<?xml version="1.0" encoding="UTF-8"?> <rss
version="0.92"> <channel> <title>Mokka mit Schlag</title>
<link>http://www.elharo.com/blog</link>
<language>en</language> <item> <title>Penn Station: Gone but
not Forgotten</title> <description> The old Penn Station in New York was
torn down before I was born. Looking at these pictures, that feels like a mistake.
The current site is functional, but no more; really just some office towers and
underground corridors of no particular interest or beauty. The new Madison Square...
</description>
<link>http://www.elharo.com/blog/new-york/2006/07/31/penn-station</link>
</item> <item> <title>Personal for Elliotte Harold</title>
<description>Some people use very obnoxious spam filters that require you to
type some random string in your subject such as E37T to get through. Needless to say
neither I nor most other people bother to communicate with these paranoids. They are
grossly overreacting to the spam problem. Personally I won't ...</description>
<link>http://www.elharo.com/blog/tech/2006/07/28/personal-for-elliotte-harold/</link>
</item> </channel> </rss>
让我们开发一个PHP页面,将任何RSS feed格式化为HTML。 清单2显示了页面外观的框架。
清单2. PHP代码的静态框架
<?php // Load and parse the XML document ?>
<html xml:lang="en" lang="en"> <head> <title><?php // The title
will be read from the RSS ?></title> </head> <body>
<h1><?php // The title will be read from the RSS again ?></h1>
<?php // Here we'll put a loop to include each item's title and description ?>
</body> </html>
解析XML文档
第一步是解析XML文档并将其存储在变量中。 这样做仅需一行代码,即可将URL传递给simplexml_load_file()
函数:
$rss =
simplexml_load_file('http://partners.userland.com/nytRss/nytHomepage.xml');
对于此示例,我从http://partners.userland.com/nytRss/nytHomepage.xml中的Userland的《纽约时报》提要填充了该页面。 当然,您可以将任何URL都用于其他RSS feed。
请注意,尽管名称为simplexml_load_ file ()
,但此函数的确会在远程HTTP URL处解析XML文档。 这也不是此功能的唯一惊喜。 返回值-此处存储在$rss
变量中-并不指向整个文档,正如您从其他API(例如文档对象模型(DOM))的经验中所期望的那样。 相反,它指向文档的根元素。 无法从SimpleXML访问文档的序言和结尾部分中的内容。
查找提要标题
整个提要的标题(不同于提要中各个故事的title
)位于rss
根元素的channel
子级的title
子级中。 您可以加载该标题,就好像XML文档只是rss
类的对象的序列化形式一样,带有本身具有标题字段的channel字段。 使用常规PHP对象引用语法,此语句查找标题:
$title = $rss->channel->title;
找到标题后,您必须将其添加到输出HTML中。 这样做很容易:只需回显$title
变量:
<title><?php echo $title; ?></title>
此行输出元素的字符串值,而不是整个元素。 即,写入了文本内容,但是没有写入标签。
您甚至可以完全跳过中间的$title
变量:
<title><?php echo $rss->channel->title;
?></title>
因为此页面在多个地方重复使用了该值,所以我发现将其存储为描述性命名的变量更加方便。
遍历项目
接下来,您必须在Feed中找到项目。 执行此任务的表达式很明显:
$rss->channel->item
但是,提要通常包含不止一项。 甚至可能都不存在。 因此,此语句返回一个数组,您可以使用for-each
循环遍历该数组:
foreach ($rss->channel->item as $item) { echo "<h2>" .
$item->title . "</h2>"; echo "<p>" . $item->description .
"</p>"; }
您可以通过从RSS提要中读取link
元素值来轻松添加链接。 只需从PHP输出a
元素,然后使用$item->link
检索URL。 清单3添加了此元素,并填充了清单1中的框架。
清单3.一个简单但完整PHP RSS阅读器
<?php // Load and parse the XML document
$rss = simplexml_load_file('http://partners.userland.com/nytRss/nytHomepage.xml');
$title = $rss->channel->title; ?> <html xml:lang="en" lang="en">
<head> <title><?php echo $title; ?></title> </head>
<body> <h1><?php echo $title; ?></h1> <?php // Here we'll
put a loop to include each item's title and description foreach
($rss->channel->item as $item) { echo "<h2><a href='" .
$item->link . "'>" . $item->title . "</a></h2>"; echo
"<p>" . $item->description . "</p>"; } ?> </body>
</html>
这就是用PHP编写简单的RSS阅读器所需要的一切-几行HTML和几行PHP。 不算空格,总共只有20行。 当然,这不是功能最丰富,最优化或最可靠的实现。 让我们看看我们可以做些什么来解决这个问题。
错误处理
并非所有RSS feed的格式都应该像预期的那样良好。 XML规范要求处理器在检测到格式正确的错误后立即停止处理文档,而SimpleXML是合格的XML处理器。 但是,当发现错误时,它不会给您很多帮助。 通常,它在php-errors文件中记录一个警告(但没有详细的错误消息),并且simplexml-load-file()
函数返回FALSE。 如果您不确定要解析的文件格式正确,请在使用文件数据之前检查此错误,如清单4所示。
清单4.注意格式错误的输入
<?php $rss =
simplexml_load_file('http://www.cafeaulait.org/today.rss'); if ($rss) { foreach
($rss->xpath('//title') as $title) { echo "<h2>" . $title . "</h2>";
} } else { echo "Oops! The input is malformed!"; } ?>
libxml_get_errors()
方法将返回更多有用的信息,调试有关问题的信息,尽管这些通常不是您要向最终阅读者显示的详细信息。
另一个常见的错误情况是文档的格式确实正确,但并不完全包含您期望的元素。 如果某项目没有标题(例如至少一个前100个RSS提要的情况),那么诸如$doc->rss->channel->item->title
这样的表达式会发生什么? 最简单的方法始终是将返回值视为数组并在其上循环。 在这种情况下,您可以确定元素的数量是否超出预期。 但是,如果您知道只希望文档中的第一个元素(即使有多个元素),则可以按索引(从零开始)进行索取。 例如,要请求第一项的标题,您可以编写:
$doc->rss->channel->item[0]->title[0]
如果没有第一项,或者第一项没有标题,则将该项目与PHP数组中的任何其他越界索引相同。 也就是说,结果为null,当您尝试将其插入输出HTML时,该结果将转换为空字符串。
识别和拒绝您不准备处理的意外格式通常是验证XML解析器的工作量。 但是,SimpleXML无法针对文档类型定义(DTD)或架构进行验证。 它仅检查格式是否正确。
处理名称空间
现在,许多站点都从RSS切换到Atom。 清单5显示了一个Atom文档的示例。 在许多方面,该文档类似于RSS示例。 但是,元数据更多,并且根元素是feed
而不是rss
。 feed
元素具有条目而不是项目。 content
元素替换了description
元素。 最重要的是,Atom文档使用名称空间,而RSS文档则没有。 这样,Atom文档可以在其内容中嵌入真实的,未转义的可扩展HTML(XHTML)。
清单5. Atom文档
<?xml version="1.0"?> <feed
xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"
xml:base="http://www.cafeconleche.org/today.atom">
<updated>2006-08-04T16:00:04-04:00</updated>
<id>http://www.cafeconleche.org/</id> <title>Cafe con Leche XML
News and Resources</title> <link rel="self" type="application/atom+xml"
href="/today.atom"/> <rights>Copyright 2006 Elliotte Rusty
Harold</rights> <entry> <title>Steve Palmer has posted a beta of
Vienna 2.1, an open source RSS/Atom client for Mac OS X. </title> <content
type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml"
id="August_1_2006_25279" class="2006-08-01T07:01:19Z"> <p> Steve Palmer has
posted a beta of <a shape="rect"
href="http://www.opencommunity.co.uk/vienna21.php">Vienna 2.1</a>, an open
source RSS/Atom client for Mac OS X. Vienna is the first reader I've found
acceptable for daily use; not great but good enough. (Of course my standards for
"good enough" are pretty high.) 2.1 focuses on improving the user interface with a
unified layout that lets you scroll through several articles, article filtering
(e.g. read all articles since the last refresh), manual folder reordering, a new get
info window, and an improved condensed layout. </p> </div>
</content> <link href="/#August_1_2006_25279"/>
<id>http://www.cafeconleche.org/#August_1_2006_25279</id>
<updated>2006-08-01T07:01:19Z</updated> </entry> <entry>
<title>Matt Mullenweg has released Wordpress 2.0.4, a blog engine based on PHP
and MySQL. </title> <content type="xhtml"> <div
xmlns="http://www.w3.org/1999/xhtml" id="August_1_2006_21750"
class="2006-08-01T06:02:30Z"> <p> Matt Mullenweg has released <a
shape="rect" href="http://wordpress.org/development/2006/07/wordpress-204
/">Wordpress 2.0.4</a>, a blog engine based on PHP and MySQL. 2.0.4 plugs
various security holes, mostly involving plugins. </p> </div>
</content> <link href="/#August_1_2006_21750"/>
<id>http://www.cafeconleche.org/#August_1_2006_21750</id>
<updated>2006-08-01T06:02:30Z</updated> </entry> </feed>
尽管元素名称已更改,但是使用SimpleXML处理Atom文档的基本方法与处理RSS的方法相同。 一个区别是,您现在必须在请求命名元素和本地名称时指定名称空间统一资源标识符(URI)。 这是一个两步过程:首先,通过将名称空间URI传递给children()
函数,请求给定名称空间中的子元素。 然后,在该名称空间中请求具有正确本地名称的元素。 假设您首先将Atom提要加载到变量$feed
,如下所示:
$feed =
simplexml_load_file('http://www.cafeconleche.org/today.atom');
现在这两行找到title
元素:
$children = $feed->children('http://www.w3.org/2005/Atom');
$title = $children->title;
您可以根据需要将此代码压缩为一个语句,尽管该行有点长。 命名空间中的所有其他元素必须类似地处理。 清单6显示了一个完整PHP页面,该页面显示来自命名空间Atom提要的标题。
清单6.一个简单PHP Atom标题阅读器
<?php $feed =
simplexml_load_file('http://www.cafeconleche.org/today.atom'); $children =
$feed->children('http://www.w3.org/2005/Atom'); $title = $children->title;
?> <html xml:lang="en" lang="en"> <head> <title><?php echo
$title; ?></title> </head> <body> <h1><?php echo
$title; ?></h1> <?php $entries = $children->entry; foreach ($entries
as $entry) { $details = $entry->children('http://www.w3.org/2005/Atom'); echo
"<h2>" . $details->title . "</h2>"; } ?> </body>
</html>
混合内容
为什么在此示例中仅显示标题? 因为在Atom中,条目的内容可以包含故事的全文-不仅可以包含纯文本,还可以包含所有标记。 这是一种叙事结构 :连续的单词供人们阅读。 像大多数此类数据一样,它具有很多混合的内容。 XML不再那么简单了,因此SimpleXML方法开始显示出一些缺陷。 它无法以任何合理的方式处理混合内容,因此这种遗漏将其排除在许多用例之外。
您可以做一件事,但这只是部分解决方案,并且只能工作,因为content
元素包含真实的XHTML。 您可以使用asXML()
函数将XHTML作为未解析的源代码直接复制到输出中,如下所示:
echo "<p>" . $details->content->asXML() .
"</p>";
生成的内容类似于清单7 。
清单7. asXML的输出
<content type="xhtml"> <div
xmlns="http://www.w3.org/1999/xhtml" id="August_7_2006_31098"
class="2006-08-07T09:38:18Z"> <p> Nikolai Grigoriev has released <a
shape="rect" href="http://www.grigoriev.ru/svgmath">SVGMath 0.3</a>, a
presentation MathML formatter that produces SVG written in pure Python and published
under an MIT license. According to Grigoriev, "The new version can work with
multiple-namespace documents (e.g. replace all MathML subtrees with SVG in an XSL-FO
or XHTML document); configuration is made more flexible, and several bugs are fixed.
There is also a stylesheet to adjust the vertical position of the resulting SVG
image in XSL-FO." </p> </div> </content>
这不是纯XHTML。 content
元素从Atom文档中剔除,而您实际上宁愿没有它。 更糟糕的是,它带有错误的名称空间,因此无法识别它的含义。 幸运的是,这个额外的元素并没有造成太大的实际危害,因为Web浏览器只是忽略了它们无法识别的任何标签。 完成的文档是无效的,但这并不重要。 如果确实困扰您,请使用字符串操作将其删除,如下所示:
$description = $details->content->asXML(); $tags =
array('<content type="xhtml"'>", "</content>"); $notags = array("", "");
$description = str_replace($tags, $notags, $description);
为了使此代码更加健壮,请使用正则表达式,而不要假设start-tag完全如上所述。 特别是,您可以考虑多种可能的属性:
// end-tag is fixed in form so it's easy to replace $description =
str_replace("</content>", "", $description); // remove start-tag, possibly
including attributes and white space $description =
ereg_replace("<content[^>]*>", "", $description);
即使有了这一改进,您的代码仍然可以跳到注释,处理指令和CDATA部分上。 无论如何切片,恐怕这不再那么简单了。 混合内容只是超出了SimpleXML设计要处理的范围。
XPath
只要您确切知道文档中的哪些元素以及它们的确切位置,诸如$rss->channel->item->title
类的表达式就很好。 但是,您并不总是知道这一点。 例如,在XHTML中,标题元素( h1
, h2
, h3
等)可以是body
子元素, div
, table
和其他几个元素。 此外, div
, table
, blockquote
以及其他元素可以相互嵌套多次。 对于许多不确定性的用例,使用XPath表达式(如//h1
或//h1[contains('Ben')]
更加容易。 SimpleXML通过xpath()
函数启用此功能。
清单8显示了一个PHP页面,该页面列出了RSS文档中的所有标题-供稿本身的标题和各个项目的标题。
清单8.使用XPath查找标题元素
<html xml:lang="en" lang="en"> <head>
<title>XPath Example</title> </head> <body> <?php $rss =
simplexml_load_file('http://partners.userland.com/nytRss/nytHomepage.xml'); foreach
($rss->xpath('//title') as $title) { echo "<h2>" . $title . "</h2>";
} ?> </body> </html>
SimpleXML仅支持XPath位置路径和位置路径的并集。 它不支持不返回节点集的XPath表达式,例如count(//para)
或contains(title)
。
从PHP 5.1版开始,SimpleXML可以对命名空间文档进行XPath查询。 与XPath中一样,即使搜索的文档使用默认名称空间,位置路径也必须使用名称空间前缀。 registerXPathNamespace()
函数将前缀与名称空间URI关联,以在下一个查询中使用。 例如,如果您想在Atom文档中找到所有title
元素,则可以使用清单9中的代码。
清单9.将XPath与名称空间一起使用
$atom =
simplexml_load_file('http://www.cafeconleche.org/today.atom');
$atom->registerXPathNamespace('atm', 'http://www.w3.org/2005/Atom'); $titles =
$atom->xpath('//atm:title'); foreach ($titles as $title) { echo "<h2>" .
$title . "</h2>"; }
最后一条警告:PHP中的XPath相当慢。 当我切换到该XPath表达式时,即使在未加载的本地服务器上,页面加载也从基本上不明显变为几秒钟。 如果使用这些技术,则必须使用某种缓存来获得合理的性能。 动态生成每个页面将无法正常工作。
结论
如果您不需要处理混合内容,SimpleXML是PHP程序员工具包的有用补充。 那涵盖了很多用例。 特别是,它适用于简单的类似记录的数据。 只要文档不是太深,太复杂并且没有混合的内容,SimpleXML比DOM替代要容易得多。 尽管XPath可以大大放松该要求,但如果您提前知道您的文档结构也将有所帮助。 缺少验证和缺乏对混合内容的任何支持令人不安,但并不总是残酷的。 许多简单格式没有混合的内容,许多用例仅涉及非常可预测的数据格式。 如果这描述了您的工作,那么您应该自己尝试SimpleXML。 稍加注意错误处理并在缓存方面做出一些努力来缓解性能问题,SimpleXML可以成为从PHP内部处理XML的可靠且健壮的方法。
翻译自: https://www.ibm.com/developerworks/opensource/library/x-simplexml/index.html
php simplexml