«
用PHP将XML转换成JSON

时间:2008-5-31    作者:Deri    分类: 分享


    <p>  <a href="http://file.ddvip.com/2007_08/1187715843_ddvip_3511.zip">本文示例源代码或素材下载</a></p>

   <p>  Asynchronous JavaScript + XML (Ajax) 的出现重新点燃了 Web 应用程序开发的热情,让很多架构师和开发人员重新考虑创建 Web 应用程序的方式。JavaScript Object Notation (JSON) 是一种用于表示在浏览器上运行的业务逻辑数据的数据交换格式。很多 Ajax 开发人员喜欢在浏览器端 JavaScript 代码中直接使用 JSON 处理数据。随着 JSON 的应用越来越多,有必要建立一个中间件服务器程序以 JSON 而非 XML 格式向浏览器提供企业应用程序数据。就是说开发人员需要将现有的 XML 编码的服务器端企业数据在发送给浏览器之前转换成 JSON。本文介绍了如何使用基于 PHP 的服务器程序,将 XML 格式的应用程序数据在发送到浏览器应用程序之前转换成 JSON 格式。</p><p>  XML 基础</p><p>  XML 是定义标记的标准。基于 XML 的标记用于描述通过非预定义标记表示的数据。因为可以根据需要发明新的标记,所以 XML 具有很好的可扩展性。清单 1 显示了用 XML 表示的数据结构的例子。</p><p>  清单 1. XML 数据的简单例子<code><?xml version="1.0" encoding="UTF-8"?><br /><contacts><br /><contact id="1"><br /><name>John Doe</name><br /><phone>123-456-7890</phone><br /><address><br /><street>123 JFKStreet</street><br /><city>Any Town</city><br /><state>Any State</state><br /><zipCode>12345</zipCode><br /></address><br /></contact><br /></contacts></code></p><p>  第一行是 XML 声明,指定了 XML 版本和使用的字符编码。接下来是根元素 <contacts>,它包括几个孩子元素。互相嵌套的孩子元素共同定义了联系人的数据。其中,<address> 元素还有自己的孩子,形成了 <contact> 元素下的子树结构。XML 还允许在起始标记中使用属性,提供元素的其他信息。<contact> 元素有一个属性,为该元素分配 id 属性。XML 文档以根元素的结束标记 </contacts> 作为结束的标志。</p>
<p> </p>

   <p>  JSON 基础</p><p>  和 XML 不同,JSON 不是一种标记语言。事实上,它是一种基于文本的数据交换格式。它是 JavaScript 对象和数组的数据语法。简单地说,JSON 可以简化数据的表示。比方说用花括号({})将对象括起来,用方括号([])将数组括起来。不需要任何特殊的解析或者额外的数据转换,JavaScript 代码很容易使用 JSON 编码的数据。清单 2 显示了 JSON 格式表示的数据结构。</p><p>  清单 2. JSON 数据的简单例子<code>{<br />"contacts" : {<br />"contact" : {<br />"@attributes" : {<br />"id" : "1"<br />},<br />"name" : "John Doe",<br />"phone" : "123-456-7890",<br />"address" : {<br />"street" : "123 JFK Street",<br />"city" : "Any Town",<br />"state" : "Any State",<br />"zipCode" : "12345"<br />}<br />}<br />}<br />}</code></p><p>  可以看到,清单 1 中的 XML 例子所示的所有数据在清单 2 中的 JSON 例子中也存在。区别在于 JSON 如何使用 JavaScript 对象和数组数据类型编码数据。数据结构首先从一个包含属性 "contacts" 的对象开始,该属性本身也是一个对象。该对象有一个属性 "contact",它本身也是一个对象。该对象有几个属性,给出了联系人的详细信息。需要注意的是,"contact" 对象包括另一个(嵌套)对象 "address",描述详细的地址。和 XML 一样,JSON 格式的数据也是自我描述的,便于人和机器阅读。</p><p>  浏览器端数据处理</p>
 <p> </p>

   <p>  现在我们来看看浏览器端代码中是如何处理数据的。服务器将 XML 数据发送到浏览器时,使用文档对象模型(DOM)API 来处理这些 XML 数据。但是,JavaScript 开发人员必须了解 XML 处理的所有复杂之处,编写很多额外的代码来解析 XML 数据,然后才能用这些数据来实现相关操作。但是发明了 JSON 之后,服务器端代码可以包装和发送 JSON 编码的应用程序数据来响应浏览器请求。如果服务器端代码向浏览器发送 JSON 格式的数据,JavaScript 开发人员就不再需要额外编写复杂的逻辑来解析 XML 了。此外,在浏览器端代码中,收到的JSON 格式的数据很容易被作为原生 JavaScript 数据结构处理。清单 3 中的代码说明处理 JSON 格式的数据不需要任何额外的操作。</p><p>  清单 3. 处理从服务器收到的 JSON 格式数据的代码片段<code>var result = httpRequest.responseText;<br />jsonResponse = eval('(' + result + ')');</code></p><p>  清单 3 中的浏览器端代码片段中,result 是从服务器接收到的 JSON 格式的字符串。通过使用 eval 语句对 JSON 格式的字符串求值,只需要一行代码就能将该字符串数据转化成原生 JavaScript 数据类型。可以看到,在浏览器端处理 JSON 数据要简单得多。使用 JSON 的意义在于简化浏览器端代码。</p><p>  清单 3 中的 eval 语句 将执行服务器返回的所有代码,因此这里存在安全风险。但风险是有限的,因为浏览器的安全策略限制了 JavaScript 的 HTTP 请求,必须是发往作为最初代码加载来源的同一台服务器。但有时候,把从服务器收到的数据传递给 eval 语句之前,检查其是否符合预期目标可能更谨慎一些,比如使用正则表达式。</p><p>  XML 到 JSON 的转换</p><p>  越来越多的应用程序需要将 XML 转换成 JSON。已经出现了一些基于 Web 的服务来执行这类转换。IBM T.J. Watson Research Center 开发了一种专门的方法,使用 PHP 进行这种转换。该方法以 XML 字符串数据为输入并将其转换成 JSON 格式的数据输出。这种 PHP 的解决方案有以下几方面的优点:</p>
 <p> </p>

   <p>  可以独立模式运行,在命令行下执行。</p><p>  可以包含到已有服务器端代码工件中。</p><p>  很容易承载为 Web 上的 Web 服务。</p><p>  XML 到 JSON 的转换需要用到两种 PHP 核心特性:</p><p>  SimpleXMLElement</p><p>  Services_JSON</p><p>  PHP 5 及更高版本支持 SimpleXMLElement。这个对象的属性包含 XML 文档中的所有数据。Services_JSON 是将近 2005 年年底批准的一项 PHP 开放源码提议。它提供了 JSON 专用的编码器(PHP 数据到 JSON)和解码器(JSON 到 PHP 数据)功能。这个开放源码的 PHP 类库可以通过 PEAR Web 站点下载。</p><p>  只需要这两种 PHP 核心特性,就可以将任何 XML 数据转化成 JSON。首先,需要使用 SimpleXMLElement 将 XML 内容转化成适当的 PHP 数据类型。然后将 PHP 数据提供给 Services_JSON 编码器,后者再生成最终的 JSON 格式的输出。</p><p>  理解 PHP 代码</p><p>  这个 xml2json 实现包括三部分:</p><p>  xml2json.php ―― 这个 PHP 类包括两个静态函数</p><p>  xml2json_test.php ―― 执行xml2json 转换函数的测试驱动程序</p><p>  test1.xml、test2.xml、test3.xml、test4.xml ―― 复杂程度不同的 XML 文件</p><p>  为了简化起见,本文省略了代码中的详细注释。不过后面附的源文件中包含完整的注释。要了解完全的程序逻辑细节,请参阅所附的源文件。</p><p>  清单 4 定义了一些要用到的常量。第一行代码导入了 Services_JSON 实现。</p><p>  清单 4. 定义 xml2json.php 中的常量<code>require_once 'json/JSON.php';<br />// Internal program-specific Debug option.<br />define ("DEBUG", false);<br />// Maximum Recursion Depth that we can allow.<br />define ("MAX_RECURSION_DEPTH_ALLOWED", 25);<br />// An empty string<br />define ("EMPTY_STR", "");<br />// SimpleXMLElement object property name for attributes<br />define ("SIMPLE_XML_ELEMENT_OBJECT_PROPERTY_FOR_ATTRIBUTES", "@attributes");<br />// SimpleXMLElement object name.<br />define ("SIMPLE_XML_ELEMENT_PHP_CLASS", "SimpleXMLElement");</code></p>
 <p> </p>

   <p>  清单 5 中的代码片段是 xml2json 转换器的入口函数。它接收 XML 数据作为输入,将 XML 字符串转化成 SimpleXMLElement 对象,然后发送给该类的另一个(递归)函数作为输入。这个函数将 XML 元素转化成 PHP 关联数组。这个数组再被传给 Services_JSON 编码器作为其输入,该编码器给出 JSON 格式的输出。</p><p>  清单 5. 使用 xml2json.php 中的 Services_JSON<code>public static function transformXmlStringToJson($xmlStringContents) {<br />$simpleXmlElementObject = simplexml_load_string($xmlStringContents);<br />  if ($simpleXmlElementObject == null) {<br />return(EMPTY_STR);<br />}<br />  $jsonOutput = EMPTY_STR;<br />  // Let us convert the XML structure into PHP array structure.<br />$array1 = xml2json::convertSimpleXmlElementObjectIntoArray($simpleXmlElementObject);<br />  if (($array1 != null) && (sizeof($array1) > 0)) {<br />// Create a new instance of Services_JSON<br />$json = new Services_JSON();<br />// Let us now convert it to JSON formatted data.<br />$jsonOutput = $json->encode($array1);<br />} // End of if (($array1 != null) && (sizeof($array1) > 0))<br />  return($jsonOutput);<br />} // End of function transformXmlStringToJson</code></p><p>  清单 6 中这段长长的代码片段采用了 PHP 开放源码社区提出的递归技术。它接收输入的 SimpleXMLElement 对象,沿着嵌套的 XML 树递归遍历。将访问过的 XML 元素保存在 PHP 关联数组中。可以通过修改清单 4 中定义的常量来改变最大递归深度。</p>
 <p> </p>

   <p>  清单 6. xml2json.php 中的转换逻辑<code>public static function convertSimpleXmlElementObjectIntoArray($simpleXmlElementObject,<br />&$recursionDepth=0) {<br />// Keep an eye on how deeply we are involved in recursion.<br />  if ($recursionDepth > MAX_RECURSION_DEPTH_ALLOWED) {<br />// Fatal error. Exit now.<br />return(null);<br />}<br />  if ($recursionDepth == 0) {<br />if (get_class($simpleXmlElementObject) != SIMPLE_XML_ELEMENT_PHP_CLASS) {<br />// If the external caller doesn't call this function initially<br />// with a SimpleXMLElement object, return now.<br />return(null);<br />} else {<br />// Store the original SimpleXmlElementObject sent by the caller.<br />// We will need it at the very end when we return from here for good.<br />$callerProvidedSimpleXmlElementObject = $simpleXmlElementObject;<br />}<br />} // End of if ($recursionDepth == 0) {<br />  if (get_class($simpleXmlElementObject) == SIMPLE_XML_ELEMENT_PHP_CLASS) {<br />    // Get a copy of the simpleXmlElementObject<br />    $copyOfsimpleXmlElementObject = $simpleXmlElementObject;<br />    // Get the object variables in the SimpleXmlElement object for us to iterate.<br />    $simpleXmlElementObject = get_object_vars($simpleXmlElementObject);<br />  }<br />  // It needs to be an array of object variables.<br />  if (is_array($simpleXmlElementObject)) {<br />    // Is the array size 0? Then, we reached the rare CDATA text if any.<br />    if (count($simpleXmlElementObject) <= 0) {<br />      // Let us return the lonely CDATA. It could even be<br />      // an empty element or just filled with whitespaces.<br />      return (trim(strval($copyOfsimpleXmlElementObject)));<br />    }<br />    // Let us walk through the child elements now.<br />    foreach($simpleXmlElementObject as $key=>$value) {<br />      // When this block of code is commented, XML attributes will be<br />      // added to the result array.<br />      // Uncomment the following block of code if XML attributes are<br />      // NOT required to be returned as part of the result array.<br />      /*<br />      if($key == SIMPLE_XML_ELEMENT_OBJECT_PROPERTY_FOR_ATTRIBUTES) {<br />        continue;<br />      }<br />      */<br />      // Let us recursively process the current element we just visited.<br />      // Increase the recursion depth by one.<br />      $recursionDepth++;<br />      $resultArray[$key] =<br />        xml2json::convertSimpleXmlElementObjectIntoArray($value, $recursionDepth);<br />      // Decrease the recursion depth by one.<br />      $recursionDepth--;<br />    } // End of foreach($simpleXmlElementObject as $key=>$value) {<br />    if ($recursionDepth == 0) {<br />      // That is it. We are heading to the exit now.<br />      // Set the XML root element name as the root [top-level] key of<br />      // the associative array that we are going to return to the caller of this<br />      // recursive function.<br />      $tempArray = $resultArray;<br />      $resultArray = array();<br />      $resultArray[$callerProvidedSimpleXmlElementObject->getName()] = $tempArray;<br />    }<br />    return ($resultArray);<br />  } else {<br />    // We are now looking at either the XML attribute text or<br />    // the text between the XML tags.<br />    return (trim(strval($simpleXmlElementObject)));<br />  } // End of else<br />} // End of function convertSimpleXmlElementObjectIntoArray.</code></p>
 <p> </p>

   <p>  成功遍历 XML 树之后,该函数就用 PHP 关联数组转换和存储了所有的 XML 元素(根元素和所有的孩子元素)。复杂的 XML 文档,得到的 PHP 数组也同样复杂。一旦 PHP 数组构造完成,Services_JSON 编码器就很容易将其转化成 JSON 格式的数据了。要了解其中的递归逻辑,请参阅存档的源文件。</p><p>  xml2json 测试驱动程序的实现</p><p>  清单 7 中的代码片段是一个用于执行 xml2json 转换器逻辑的测试驱动程序。</p><p>  清单 7. xml2json_test.php<code><?php<br />  require_once("xml2json.php");<br />  // Filename from where XML contents are to be read.<br />  $testXmlFile = "";<br />  // Read the filename from the command line.<br />  if ($argc <= 1) {<br />    print("Please provide the XML filename as a command-line argument:<br />");<br />    print("  php -f xml2json_test.php test1.xml<br />");<br />    return;<br />  } else {<br />    $testXmlFile = $argv[1];<br />  }<br />  //Read the XML contents from the input file.<br />  file_exists($testXmlFile) or die('Could not find file ' . $testXmlFile);<br />  $xmlStringContents = file_get_contents($testXmlFile);<br />  $jsonContents = "";<br />  // Convert it to JSON now.<br />  // xml2json simply takes a String containing XML contents as input.<br />  $jsonContents = xml2json::transformXmlStringToJson($xmlStringContents);<br />  echo("JSON formatted output generated by xml2json:<br />");<br />  echo($jsonContents);<br />?></code></p>
 <p> </p>

   <p>  可以在命令行中运行该程序,输入以下 XML 文件名作为命令行参数:</p><code>php -f xml2json_test.php test2.xml</code></p><p>  在命令行中执行的时候,该程序将 XML 内容从文件读入一个字符串变量。然后调用 xml2json 类中的静态函数得到 JSON 格式的结果。除了从命令行中运行该程序之外,还可以修改这个源文件中的逻辑来公开 xml2json 转换器,将其作为可使用简单对象访问协议(SOAP)或者 Representational State Transfer (REST) 访问协议来远程调用的 Web 服务。如果需要,在 PHP 中只要稍加修改就能实现此远程调用。</p><p>  清单 8 展示了本文提供的四个测试 XML 文件中的一个,这些文件用于测试 xml2json 实现。他们的复杂度各不相同。可以将这些文件作为命令行参数传递给测试驱动程序 xml2json_test.php。</p><p>  清单 8. 用 test2.xml 测试 xml2json 实现<code><?xml version="1.0" encoding="UTF-8"?><br /><books><br />  <book id="1"><br />    <title>Code Generation in Action</title><br />    <author><first>Jack</first><last>Herrington</last></author><br />    <publisher>Manning</publisher><br />  </book><br />  <book id="2"><br />    <title>PHP Hacks</title><br />    <author><first>Jack</first><last>Herrington</last></author><br />    <publisher>O'Reilly</publisher><br />  </book><br />  <book id="3"><br />    <title>Podcasting Hacks</title><br />    <author><first>Jack</first><last>Herrington</last></author><br />    <publisher>O'Reilly</publisher><br />  </book><br /></books></code></p>
 <p> </p>

   <p>  清单 9 中所示的代码片段是,使用 test2.xml 作为测试驱动程序 xml2json_test.php 的命令行参数时得到的 JSON 格式结果。</p><p>  清单 9. test2.xml 的 JSON 格式化结果<code>{<br />"books" : {<br />"book" : [ {<br />"@attributes" : {<br />"id" : "1"<br />},<br />"title" : "Code Generation in Action",<br />"author" : {<br />"first" : "Jack", "last" : "Herrington"<br />},<br />"publisher" : "Manning"<br />}, {<br />"@attributes" : {<br />"id" : "2"<br />},<br />"title" : "PHP Hacks", "author" : {<br />"first" : "Jack", "last" : "Herrington"<br />},<br />"publisher" : "O'Reilly"<br />}, {<br />"@attributes" : {<br />"id" : "3"<br />},<br />"title" : "Podcasting Hacks", "author" : {<br />"first" : "Jack", "last" : "Herrington"<br />},<br />"publisher" : "O'Reilly"<br />}<br />]}<br />}</code></p><p>  请注意,<book> 元素的 XML 属性 id 作为 "@attributes" 对象的属性被保存在 JSON 数据中,<book> 元素作为对象数组被保存在 JSON 数据中。JSON 输出易于在 JavaScript 代码中使用 eval 语句进行处理。</p><p>  结束语</p></p><p>  JSON 刚刚开始受到 Web 开发人员的关注。它的成功(主要体现在 JavaScript 开发人员的使用方面)源于它的典雅和简单。有些情况下使用 JSON 代替 XML 是值得的。本文总结了在中间件服务器层上需要将 XML 转化成 JSON 的情况。进一步强调了之后利用现有的 XML 编码企业数据作为 JSON 格式数据,便于浏览器端程序使用的基本原理。本文还提供了将 XML 转化成 JSON 的 PHP 代码。</p><p>  本文所提供的源代码可以通过多种方式使用:作为独立的工具、在已有服务器端程序中作为共享类库,或者作为 SOAP/REST Web 服务功能来参与企业的 Service-Oriented Architecture (SOA)。</p></p>