在 PHP 中验证 XML

确保数据完整性并在 PHP 中根据 XML 模式验证 XML 文档

PHP 开发人员通常需要在其代码中放入 Extensible Markup Language (XML) 解析器的服务。这些代码行的引入,常常又需要验证 XML 输入。幸运的是,在 PHP 内很容易就能实现这一点。本文向您展示了如何在 PHP 内验证 XML 以及如何判断验证失败的原因。

Brian M. Carey, 高级系统工程师, Triangle Information Solutions

Brian Carey 的照片Brian Carey 是一位信息系统顾问,擅长 Java、Java Enterprise、PHP、Ajax 和相关技术。您可以在 http://twitter.com/brianmcarey 的 Twitter 上了解 Brian Carey。



2009 年 12 月 28 日

为何要进行 XML 验证?

XML 是一种标记语言,允许开发人员创建自己的定制语言。这种语言可被用来以一种独立于平台的形式携带数据(并不一定显示数据)。这种语言通过 markup 标记定义,非常类似 Hypertext Markup Language (HTML)。

近些年来,XML 越来越受欢迎,因它代表了两个世界的最佳性能:它很易被人、机读懂。XML 语言采取的是一种类似树的结构,用元素和属性描述关键数据。元素和属性通常以直白的英语命名(以便人能读懂),同时它们又是高度结构化的(以便机器能够解析)。

现在,假设您创建了您自己的 XML 语言,称为 LuresXML。LuresXML 的作用是提供一种用来定义您网站上所提供的诱饵的各种类型的方式。首先,需要创建一种 XML 模式,该模式定义了 XML 文档是何模样,如 清单 1 所示。

清单 1. lures.xsd
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="lures">
 <xs:complexType> 
  <xs:sequence>
   <xs:element name="lure">
    <xs:complexType>
     <xs:sequence>
     <xs:element name="lureName" type="xs:string"/>
     <xs:element name="lureCompany" type="xs:string"/>
     <xs:element name="lureQuantity" type="xs:integer"/>
     </xs:sequence>
    </xs:complexType>
   </xs:element>
  </xs:sequence>
 </xs:complexType>
</xs:element>
</xs:schema>

很显然,这是一个非常简单的例子。根元素名为 lures。它是一个或多个 lure 元素的父元素,而每个 lure 元素又是其他三个元素的父元素。第一个元素是诱饵的名称 (lureName)。第二个元素是制造这种诱饵的公司的名称 (lureCompany)。而第三个元素则是数量 (lureQuantity) 或者该公司库存内有多少诱饵。前两个子元素以字符串定义,而 lureQuantity 元素则定义为整型。

现在,假设您想要基于该模式创建一个 XML 文档(有时又称为实例)。该文档应该类似 清单 2

清单 2. lures.xml
<lures>
 <lure>
  <lureName>Silver Spoon</lureName>
  <lureCompany>Clark</lureCompany>
  <lureQuantity>Seven</lureQuantity>
 </lure>
</lures>

以上是 清单 1 所示模式的一个简单的 XML 文档实例。在本例中,此文档实例只列出一个诱饵。这个诱饵的名称是 Silver Spoon。制造公司是 Clark。现有的库存数量是 Seven

这里有一个问题:如何知道 清单 2 所示的这个 XML 文档就是 清单 1 内所定义的 XML 模式的一个恰当实例呢?实际上,并非如此(我故意这样安排)。

请注意 清单 1 内定义的lureQuantity 元素。其类型为 xs:integer。而在 清单 2 内,lureQuantity 元素实际包含的是一个单词(Seven),而不是一个整型数。

XML 验证的目的是捕获错误的确切类型。恰当的验证应该能够确保一个 XML 文档与在其模式内定义的规则相匹配。

继续这个例子,当您尝试验证 清单 2 内的 XML 文档时,就会出现错误。在软件应用程序内使用这个文档之前,要先修复这个错误(通过将 Seven 更改为 7)。

XML 验证非常重要,因为我们想要尽快捕获信息交换过程中的错误。否则,当想要解析一个 XML 文档,而这个文档内包含无效数据或预想之外的结构时,就会出现不可预料的结果。

PHP 内简单的 XML 解析

对如何在 PHP 内解析 XML 文档的详细介绍超出了本文的讨论范围。不过,我会介绍有关在 PHP 内加载一个 XML 文档的基础知识。

为了简便起见,我们继续使用 清单 1 的模式和 清单 2 的 XML 文档。清单 3 给出了加载此 XML 文档所需的一些基础 PHP 代码。

清单 3. testxml.php
<?php

$xml = new DOMDocument(); 
$xml->load('./lures.xml'); 

?>

上述代码也丝毫不复杂。其中,我们使用 DOMDocument 类来加载这个 XML 文档,这里称之为 lures.xml。请注意要让这些代码能够用在 PHP 服务器上,lures.xml 文件必须位于与实际 PHP 代码相同的路径。

加载了 XML 文档后,很自然地就想要开始解析这个 XML 文档。但是,如您所见,最好的做法是首先验证这个文档来确保它能匹配在此模式内设置好的那些语言规范。

PHP 内简单的 XML 验证

通过添加一些简单的验证代码来继续增强 清单 3 内的 PHP 代码,如 清单 4 所示。

清单 4. 增强后的 testxml.php
<?php

$xml = new DOMDocument(); 
$xml->load('./lures.xml');

if (!$xml->schemaValidate('./lures.xsd')) { 
   echo "invalid<p/>";
} 
else { 
   echo "validated<p/>"; 
} 

?>

同样地,请再次注意 清单 2 内的模式文件必须位于与 PHP 代码相同的目录内。否则,PHP 就会返回错误。

这些新代码会针对加载此 XML 的 DOMDocument 对象调用 schemaValidate 方法。此方法只接受一个参数: 用来验证此 XML 文档的 XML 模式所在位置。此方法返回一个布尔值,若为 true,表明验证成功,若为 false,则表明验证失败。

现在,将 清单 3 的 PHP 代码部署到 PHP 服务器。之所以将它称为 testxml.php,是因为该名称已经在清单 3 和 4 内给定。请确保这个 XML 文档(来自 清单 2)和 XML 模式(来自 清单 1)均处于相同的目录内。同样地,如果不在同一个目录,PHP 就会报告错误。

让浏览器指向 testxml.php。在屏幕上会看到一个简单的单词:“invalid”。

幸好,这个模式验证能够工作。它应该会返回一个错误,而结果的确如此。

而不好之处是您根本不知道错误发生在 XML 文档内的什么位置。虽然,您可能 知道了,因为我在本文开始的时候提到过错误的出处,但是,请您假装不知道,好么?

有错误,但在哪?

我之前说过:不好之处是您根本不知道错误发生在 XML 文档内的什么位置。让我们一探究竟。如果 PHP 代码真能报告错误的位置以及错误的性质以便于您能采取正确的行动,那就更好了。比如,若能给出这样的一个说明 “Hey! I can't accept a string for lureQuantity”,就不错。

要查看遇到的这个错误,可以使用 libxml_get_errors() 函数。遗憾的是,该函数的文本输出并不能明确指明在 XML 文档内错误发生之处。相反,它指明在 PHP 代码内错误的出处。由于这没什么用处,我们需要看看另外一个选项。

这里,还有另外一个 PHP 函数,称为 libxml_use_internal_errors()。此函数只接受一个 Boolean 作为惟一参数。如果把它设为 true,那么它意味着将禁用 libxml 错误报告并自己捕获错误。而我们正是这么做的。

当然,这意味着我们还需要编写一些代码。但换来的是更具体的错误报告。长期来看,这能节省很多时间。

清单 5 给出了最终的 testxml.php。

清单 5. 最后的 testxml.php
<?php
function libxml_display_error($error) 
{ 
$return = "<br/>\n"; 
switch ($error->level) { 
case LIBXML_ERR_WARNING: 
$return .= "<b>Warning $error->code</b>: "; 
break; 
case LIBXML_ERR_ERROR: 
$return .= "<b>Error $error->code</b>: "; 
break; 
case LIBXML_ERR_FATAL: 
$return .= "<b>Fatal Error $error->code</b>: "; 
break; 
} 
$return .= trim($error->message); 
if ($error->file) { 
$return .= " in <b>$error->file</b>"; 
} 
$return .= " on line <b>$error->line</b>\n"; 

return $return; 
} 

function libxml_display_errors() { 
$errors = libxml_get_errors(); 
foreach ($errors as $error) { 
print libxml_display_error($error); 
} 
libxml_clear_errors(); 
} 

// Enable user error handling 
libxml_use_internal_errors(true); 

$xml = new DOMDocument(); 
$xml->load('./lures.xml'); 

if (!$xml->schemaValidate('./lures.xsd')) { 
print '<b>Errors Found!</b>'; 
libxml_display_errors(); 
} 
else { 
echo "validated<p/>"; 
} 

?>

首先,请注意此段代码顶部的那个函数。它名为 libxml_display_error() 并接受 LibXMLError 对象作为其惟一参数。然后,它使用再熟悉不过的开关语句来决定错误等级并相应打造与该等级相适应的错误消息。当等级确定后,代码会生成一个能报告相应等级的字符串。

之后,会发生两件事情。其一,检查此错误对象以确定 file 属性是否包含一个值。如果包含,那么该 file 值就会被追加到错误消息以便能够报告这个文件的位置。接下来,line 属性被追加到这个错误消息以便用户能够确切看到在这个 XML 文件内错误发生的位置。无需多说,这对于调试目的极其重要。

应该指出的是 libxml_display_error() 只生成一个字符串来描述此错误。将错误实际打印到屏幕则留给调用程序负责,在本例中,调用程序就是 libxml_display_errors()

其后的函数是之前提到过的 libxml_display_errors(),它不接受参数。此函数所做的第一件事情是调用 libxml_get_errors()。它会返回 LibXMLError 对象的一个数组,这些对象代表的是当 schemaValidate() 方法在 XML 文档上被调用时所遇到的全部错误。

接下来,遍历遇到的每个错误并为每个错误对象调用 libxml_display_error() 函数。由此函数返回的任何字符串都会被打印到屏幕。以这种方式处理错误的一个最大的好处是所有 这些错误都会立刻打印。这意味着只需执行代码一次即可查看特定于某个 XML 文档的所有错误。

最后,libxml_clear_errors() 通过 schemaValidate() 方法清除最近遇到的错误。这意味着如果 schemaValidate() 以相同的代码顺序再次执行,那么一切都会重新开始,并且只报告新的错误。如果不清除,并且再次执行 schemaValidate(),那么来自 schemaValidate() 的第一次调用的所有错误仍保留在由 libxml_get_errors() 返回的这个数组内。很显然,如果想要寻找的是全新的错误集,那么这么做就会有问题了。

还有重要的一点需要指出,即我对 清单 5 所示的代码底部的 if-then 语句做了稍许修改。如果遇到一个错误,它就会以黑体打印 “Errors Found!”,然后调用之前提到的 libxml_display_errors() 函数,该函数显示遇到的所有错误,之后清除这个错误数组。我倾向于采用这种方式,而不是我在 清单 4 内所做的那样,只打印出 “invalid”。

第二个测试

现在,是再次进行测试的时候了。将 清单 5 的 PHP 文件移到 PHP 服务器。保持文件名不变(仍为 testxml.php)。与之前一样,确保 XML Schema Definition (XSD) 文件及 XML 文件位于与 PHP 文件相同的目录内。再次让浏览器指向 testxml.php,现在,应该能够看到类似下面的内容:

Errors Found!
Error 1824: Element 'lureQuantity': 'Seven' is not a valid value of the atomic type 'xs:integer'. in /home/thehope1/public_html/example.xml on line 5

没错,这已经描述得很好了,不是么?这些错误消息告诉我们错误发生在哪行。它还告诉我们文件位于何处(好像您不知道一样)。并且它还告诉我们错误为何发生。而这些正是我们需要的信息。

修复问题

现在,我们可以不用理会这个 PHP 文件,开始致力于修复 XML 文档内的问题。

据报告错误发生在 XML 文档的第 5 行,所以最好是查看第 5 行,看看那里有何异样。毫不奇怪,第 5 行就是 lureQuantity 元素所在的位置。并且,如能仔细查看一下,不难发现 Seven 是一个字符串,而非数值。所以将字符串 Seven 更改为数值 7。XML 文档的最后版本应该类似 清单 6

清单 6. 更新后的 XML 文件
<lures>
 <lure>
  <lureName>Silver Spoon</lureName>
  <lureCompany>Clark</lureCompany>
  <lureQuantity>7</lureQuantity>
 </lure>
</lures>

现在,将这个新的 XML 文件复制到 PHP 服务器。并且,同样地,将浏览器指向 testxml.php。应该会看到一个单词:“validated”。这是一个绝好的消息,原因有两个。第一,它意味着验证代码能正常工作,因 XML 文档的确无误。第二,您已经在 PHP 内验证了您的第一个 XML 文档。祝贺您!

正如我一直建议的,现在应该进行一些修补工作。修改 lures.xsd,使其成为一个更复杂些的模式。修改 lures.xml,使其成为该模式更复杂些的实例。将这些文件复制到 PHP 服务器,并且再次执行 testxml.php。看看会发生些什么。故意出于不同的原因生成一个无效文档并看看会发生什么。

并且,请注意当进行修补时,根本无需更改 PHP 代码。只需确保文件名(lures.xml 和 lures.xsd)相同,就可以随心所欲地修改它们了。

结束语

PHP 为开发人员简化了 XML 文档的验证。联合使用 DOMDocument 类与 schemaValidate() 方法,就能确保您的 XML 文档符合其各自的模式的规范。这对于确保您的软件应用程序内的数据完整性至关重要。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source
ArticleID=458976
ArticleTitle=在 PHP 中验证 XML
publish-date=12282009