PageRenderTime 28ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/commonsapi/commonsapi.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 535 lines | 435 code | 70 blank | 30 comment | 105 complexity | 7433d35298398f9eb368ee526417c62c MD5 | raw file
  1. <?PHP
  2. /*
  3. Wikimedia Commons image API
  4. (c) 2008 by Magnus Manske
  5. Released under GPL
  6. RUns at http://tools.wikimedia.de/~magnus/commonsapi.php
  7. */
  8. /* USEFUL TEST IMAGES
  9. ChathamHDY0016.JPG has location
  10. Sa-warthog.jpg has many languages, featured
  11. Gesammelte_Werke_(Thoma)_1_307.jpg non-{{Information}} template
  12. */
  13. $api_version = '0.9' ; // Version
  14. $api_data_page_url = 'http://commons.wikimedia.org/w/index.php?title=MediaWiki:Commons_API&action=raw' ; // URL of license data page
  15. $license_data = array () ; // License detail data
  16. $ignore_categories = array () ; // Do not include these categories in the list
  17. # Scans the raw text from http://commons.wikimedia.org/w/index.php?title=MediaWiki:Commons_API
  18. # and uses it as data
  19. function read_from_api_page () {
  20. global $testing , $license_data , $ignore_categories , $api_data_page_url ;
  21. $text = file_get_contents ( $api_data_page_url ) ;
  22. $license_data = array () ;
  23. $ignore_categories = array () ;
  24. $h = array () ;
  25. for ( $a = 0 ; $a < 9 ; $a++ ) $h[$a] = '' ;
  26. $lines = explode ( "\n" , $text ) ;
  27. foreach ( $lines AS $l ) {
  28. $l = trim ( $l ) ;
  29. if ( $l == '' ) continue ; # Ignore blank lines
  30. if ( substr ( $l , 0 , 1 ) == '=' ) { # Heading
  31. $cnt = 0 ;
  32. while ( substr ( $l , 0 , 1 ) == '=' ) {
  33. $l = substr ( $l , 1 , strlen ( $l ) - 2 ) ;
  34. $cnt++ ;
  35. }
  36. $l = trim ( $l ) ;
  37. $l = str_replace ( '_' , ' ' , $l ) ;
  38. if ( $cnt == 2 ) $l = strtolower ( $l ) ;
  39. $h[$cnt] = $l ;
  40. for ( $a = $cnt+1 ; $a < 9 ; $a++ ) $h[$a] = '' ;
  41. continue ;
  42. }
  43. if ( $h[2] == '' ) continue ; # No section level 2, ignore
  44. if ( $h[2] == 'license data' ) {
  45. if ( substr ( $l , 0 , 1 ) != ';' ) continue ; # Something else...
  46. $l = substr ( $l , 1 ) ;
  47. $l = explode ( ':' , $l , 2 ) ;
  48. $key = array_shift ( $l ) ;
  49. $value = array_shift ( $l ) ;
  50. $key = str_replace ( ' ' , '_' , $key ) ;
  51. if ( !isset ( $license_data[$h[3]] ) ) $license_data[$h[3]] = array () ;
  52. if ( $key == 'based_on' ) {
  53. $license_data[$h[3]] = $license_data[$value] ;
  54. } else $license_data[$h[3]][$key] = $value ;
  55. } elseif ( $h[2] == 'ignore categories' ) {
  56. if ( substr ( $l , 0 , 1 ) != '*' ) continue ;
  57. $l = substr ( $l , 1 ) ;
  58. $l = str_replace ( '_' , ' ' , $l ) ;
  59. $ignore_categories[] = trim ( $l ) ;
  60. }
  61. }
  62. }
  63. // Function to convert text in attributes and between tags to XML by changing some charactes to entities
  64. function myurlencode ( $text ) {
  65. $text = str_replace ( '<a href="/w' , '<a href="http://commons.wikimedia.org/w' , $text ) ;
  66. return str_replace ( array ( '&', '"', "'", '<', '>' ), array ( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;' ), $text );
  67. }
  68. // End with XML error
  69. function end_run ( $text ) {
  70. global $api_version ;
  71. header('Content-type: text/xml; charset=utf-8');
  72. print '<?xml version="1.0" encoding="UTF-8"?>' ;
  73. print '<response version="' . $api_version . '">' ;
  74. print "<error>$text</error>" ;
  75. print '</response>' ;
  76. exit ;
  77. }
  78. function wiki2html ( &$wiki ) {
  79. global $force_html , $img ;
  80. if ( !$force_html ) return ;
  81. if ( $wiki == '' ) return ;
  82. if ( false === strpos ( $wiki , '[[' ) && false === strpos ( $wiki , '{|' ) && false === strpos ( $wiki , '{{' ) && false === strpos ( $wiki , "''" ) ) return ;
  83. $wiki = trim ( $wiki ) ;
  84. $url = "http://commons.wikimedia.org/w/api.php?format=php&action=parse&text=" . urlencode ( $wiki ) . "&title=" . urlencode ( $img ) ;
  85. $wiki = unserialize ( file_get_contents ( $url ) ) ;
  86. $wiki = $wiki['parse']['text']['*'] ;
  87. }
  88. function print_tag ( $tag , $text ) {
  89. if ( !isset ( $text ) ) print "<$tag/>" ;
  90. else print "<$tag>" . myurlencode ( $text ) . "</$tag>" ;
  91. }
  92. function get_same_level_html ( $t ) {
  93. $cnt = 0 ;
  94. for ( $pos = 0 ; $pos < strlen ( $t ) ; $pos++ ) {
  95. if ( $t[$pos] == '<' && $t[$pos+1] == '/' && $cnt == 0 ) return substr ( $t , 0 , $pos - 1 ) ;
  96. if ( $t[$pos] == '<' ) {
  97. if ( $t[$pos+1] == '/' ) $cnt-- ; // Closing
  98. else if ( $t[$pos+1] == '!' ) ; // Comment
  99. else $cnt++ ; // Opening
  100. } else if ( $t[$pos] == '>' ) {
  101. if ( $t[$pos-1] == '/' ) $cnt-- ; // Self-closing
  102. }
  103. }
  104. return $t ;
  105. }
  106. function try_information_template ( $text ) {
  107. global $titles ;
  108. $matches = array () ;
  109. preg_match_all ( '/span\s+id="field-[^"]+"/' , $text , $matches ) ;
  110. $matches = $matches[0] ;
  111. if ( count ( $matches ) == 0 ) return false ;
  112. $ret = false ;
  113. foreach ( $matches AS $m ) {
  114. $t = array_pop ( explode ( $m , $text , 2 ) ) ;
  115. $title = array () ;
  116. preg_match ( '/\s+title="[^"]*"/' , $t , $title ) ;
  117. $title = $title[0] ;
  118. $title = array_pop ( explode ( 'title="' , $title , 2 ) ) ;
  119. $title = substr ( $title , 0 , -1 ) ;
  120. $title = urldecode ( $title ) ;
  121. $k = explode ( 'field-' , $m ) ;
  122. $k = str_replace ( '"' , '' , $k[1] ) ;
  123. $titles[$k] = $title ;
  124. $ret = true ;
  125. }
  126. wiki2html ( $titles['description'] ) ;
  127. wiki2html ( $titles['date'] ) ;
  128. wiki2html ( $titles['author'] ) ;
  129. wiki2html ( $titles['source'] ) ;
  130. wiki2html ( $titles['permission'] ) ;
  131. wiki2html ( $titles['otherversions'] ) ;
  132. return $ret ;
  133. }
  134. function try_fileinfo_template ( $text ) {
  135. global $titles ;
  136. $matches = array () ;
  137. preg_match_all ( '/th[^>]+id="fileinfotpl_[^"]+"/' , $text , $matches ) ;
  138. $matches = $matches[0] ;
  139. if ( count ( $matches ) == 0 ) return false ;
  140. # header('Content-type: text/plain; charset=utf-8');
  141. $ret = false ;
  142. $t2 = array () ;
  143. foreach ( $matches AS $m ) {
  144. $k = array_pop ( explode ( 'fileinfotpl_' , $m , 2 ) ) ;
  145. $k = array_shift ( explode ( '"' , $k , 2 ) ) ;
  146. $t = array_pop ( explode ( $m , $text , 2 ) ) ;
  147. $t = array_pop ( explode ( '<td' , $t , 2 ) ) ;
  148. $t = array_pop ( explode ( '>' , $t , 2 ) ) ;
  149. $t = get_same_level_html ( $t ) ;
  150. if ( substr ( $t , 0 , 3 ) == '<b>' ) $t = substr ( $t , 3 ) ;
  151. if ( substr ( $t , -4 , 4 ) == '</b>' ) $t = substr ( $t , 0 , -4 ) ;
  152. if ( isset ( $t2[$k] ) ) {
  153. if ( $t2[$k] != $t ) $t2[$k] .= "\n$t" ;
  154. } else $t2[$k] = $t ;
  155. $ret = true ;
  156. }
  157. $titles['description'] = array() ;
  158. $titles['date'] = array() ;
  159. $titles['author'] = array() ;
  160. $titles['source'] = array() ;
  161. $titles['permission'] = array() ;
  162. foreach ( $t2 AS $k => $v ) {
  163. if ( $k == 'aut' ) $titles['author'][] = $v ;
  164. if ( $k == 'desc' ) $titles['description'][] = $v ;
  165. if ( $k == 'date' ) $titles['date'][] = $v ;
  166. if ( $k == 'src' ) $titles['source'][] = $v ;
  167. if ( $k == 'perm' ) $titles['permission'][] = $v ;
  168. }
  169. foreach ( $titles AS $k => $v ) $titles[$k] = implode ( "\n" , $v ) ;
  170. # exit;
  171. return $ret ;
  172. }
  173. function die_with_form () {
  174. print "<h1>Wikimedia Commons API</h1>
  175. <hr/>
  176. Usage : <b>commonsapi.php?image=IMAGENAME</b><br/>
  177. IMAGENAME must not have an Image: prefix <br/>
  178. Optional parameters:
  179. <ul>
  180. <li><b>&thumbwidth=XX</b> returns a URL to a thumbnail maximal XX pixel wide</li>
  181. <li><b>&thumbheight=YY</b> returns a URL to a thumbnail maximal YY pixel high (requires <b>thumbwidth</b>)</li>
  182. <li><b>&languages=en|de|default</b> returns only the languages for the listed codes; separate with \"|\" (default: all languages)</li>
  183. <li><b>&forcehtml</b> converts wiki text into HTML (slower, but gets rid of annoying wiki markup)</li>
  184. <li><b>&versions</b> adds information about all former versions of the file</li>
  185. <li><b>&meta</b> adds meta information that is stored with the file (e.g., EXIF data)</li>
  186. </ul>
  187. Examples :
  188. <ul>
  189. <li><a href='commonsapi.php?image=Sa-warthog.jpg&thumbwidth=150&thumbheight=150&versions&meta'>Sa-warthog.jpg</a> with thumbnail, version, and meta information</li>
  190. <li><a href='commonsapi.php?image=Yarra_Panorama.jpg&forcehtml'>Yarra_Panorama.jpg</a> (contains geocoding, which resolves to a <tt>&lt;location&gt;</tt> tag)</li>
  191. </ul>
  192. <i>Note:</i> All returned attributes and texts are entity-encoded (<i><tt>\"'<>&</tt></i> are replaced with XML entities).<br/><br/>
  193. (You want to help making this API better? Data source for license information is <a href=\"http://commons.wikimedia.org/wiki/MediaWiki:Commons_API\">here</a>. Bugs and feature requests go to <a href=\"http://commons.wikimedia.org/wiki/User_talk:Magnus_Manske\">this guy</a>.)
  194. " ;
  195. exit ;
  196. }
  197. # ___________________
  198. # MAIN PROGRAM
  199. // Fake user agent
  200. ini_set('user_agent','Commons API;');
  201. // Get parameters
  202. $testing = isset ( $_REQUEST['test'] ) ;
  203. $versions = isset ( $_REQUEST['versions'] ) ;
  204. $meta = isset ( $_REQUEST['meta'] ) ;
  205. $img = $_REQUEST['image'] ;
  206. $force_html = isset ( $_REQUEST['forcehtml'] ) ;
  207. $get_languages = $_REQUEST['languages'] ;
  208. $thumb_width = $_REQUEST['thumbwidth'] ;
  209. $thumb_height = $_REQUEST['thumbheight'] ;
  210. if ( !$thumb_height || !$thumb_width ) $thumb_height = 0 ;
  211. if ( !$thumb_width ) $thumb_width = 0 ;
  212. if ( !isset ( $get_languages ) ) $get_languages = '' ;
  213. if ( substr ( strtolower ( $img ) , 0 , 6 ) == 'image:' ) $img = substr ( $img , 6 ) ;
  214. // Read in license information
  215. read_from_api_page () ;
  216. // Die with explanation if no image given
  217. if ( !isset ( $img ) ) die_with_form () ;
  218. // Prepare and read rendered image page
  219. $img = str_replace ( ' ' , '_' , $img ) ;
  220. $url = "http://commons.wikimedia.org/wiki/Image:$img" ;
  221. srand(time()) ;
  222. $text = file_get_contents ( $url."?".rand() ) ;
  223. // get file data via "normal" API
  224. $ii_url = "http://commons.wikimedia.org/w/api.php?format=php&action=query&prop=imageinfo&iilimit=500&iiprop=timestamp|user|url|size|sha1|metadata&titles=Image:" . $img ;
  225. if ( $thumb_width != 0 ) $ii_url .= '&iiurlwidth=' . $thumb_width ;
  226. if ( $thumb_height != 0 ) $ii_url .= '&iiurlheight=' . $thumb_height ;
  227. $data = unserialize ( file_get_contents ( $ii_url ) ) ;
  228. $data = array_shift ( $data['query']['pages'] ) ;
  229. $data = $data['imageinfo'] ;
  230. $file_url = $data[0]['url'] ;
  231. if ( isset ( $data[0]['thumburl'] ) ) $thumb_url = $data[0]['thumburl'] ;
  232. else $thumb_url = '' ;
  233. if ( isset ( $data[0]['metadata'] ) ) $metadata = $data[0]['metadata'] ;
  234. else $metadata = array () ;
  235. $filedata = array () ;
  236. if ( !isset ( $data ) ) {
  237. end_run ( 'File does not exist' ) ;
  238. }
  239. foreach ( $data AS $k => $v ) {
  240. $fd = $v ;
  241. if ( isset ( $fd['metadata'] ) ) unset ( $fd['metadata'] ) ;
  242. if ( isset ( $fd['url'] ) ) unset ( $fd['url'] ) ;
  243. if ( isset ( $fd['comment'] ) ) unset ( $fd['comment'] ) ;
  244. $filedata[$k] = $fd ;
  245. }
  246. if ( !isset ( $filedata[0] ) ) $filedata[0] = array () ;
  247. if ( !isset ( $filedata[0]['width'] ) ) $filedata[0]['width'] = 0 ;
  248. if ( !isset ( $filedata[0]['height'] ) ) $filedata[0]['height'] = 0 ;
  249. if ( !isset ( $filedata[0]['size'] ) ) $filedata[0]['size'] = 0 ;
  250. // Info table
  251. $titles = array () ;
  252. $found = false ;
  253. if ( !$found ) $found = try_information_template ( $text ) ;
  254. if ( !$found ) $found = try_fileinfo_template ( $text ) ;
  255. // Set default description
  256. $desc = array () ;
  257. if ( isset ( $titles['description'] ) ) $desc['default'] = $titles['description'] ;
  258. unset ( $titles['description'] ) ;
  259. // Detect descriptions in many languages
  260. $language_names = array () ;
  261. $matches = array () ;
  262. preg_match_all ( '/div\s+class="description [a-z-]+"/' , $text , $matches ) ;
  263. $matches = $matches[0] ;
  264. foreach ( $matches AS $m ) {
  265. $tx = explode ( $m , $text ) ;
  266. array_shift ( $tx ) ;
  267. $m = explode ( ' ' , $m ) ;
  268. $m = array_pop ( $m ) ;
  269. $m = substr ( $m , 0 , -1 ) ;
  270. $t = $tx[0] ;
  271. $t = explode ( '</span>' , $t , 2 ) ;
  272. $ln = array_shift ( $t ) ;
  273. $ln = array_pop ( explode ( '<b>' , $ln ) ) ;
  274. $ln = array_shift ( explode ( '</b>' , $ln ) ) ;
  275. $ln = str_replace ( ':' , '' , $ln ) ;
  276. $language_names[$m] = $ln ;
  277. $t = array_pop ( $t ) ;
  278. $tx = explode ( '</div>' , $t ) ;
  279. $t = "" ;
  280. while ( count ( $tx ) > 0 ) { // Hackish; works only if there's no <div> in the description. FIXME!
  281. $t2 = array_shift ( $tx ) ;
  282. $t .= $t2 ;
  283. break ;
  284. }
  285. if ( !isset ( $desc[$m] ) ) $desc[$m] = $t ;
  286. else if ( $desc[$m] != $t ) $desc[$m] .= "<br/>\n$t" ;
  287. }
  288. if ( !isset ( $desc['default'] ) && isset ( $desc['en'] ) ) $desc['default'] = $desc['en'] ;
  289. if ( $get_languages != '' ) {
  290. $gl = explode ( '|' , $get_languages ) ;
  291. $d2 = array () ;
  292. foreach ( $gl AS $l ) {
  293. if ( isset ( $desc[$l] ) ) $d2[$l] = $desc[$l] ;
  294. }
  295. $desc = $d2 ;
  296. }
  297. // Categories
  298. $self_made = '' ;
  299. $cats = array () ;
  300. preg_match_all ( '/ title="Category:[^"]+"\s*>/' , $text , $matches ) ;
  301. $matches = $matches[0] ;
  302. foreach ( $matches AS $m ) {
  303. $m = array_pop ( explode ( ':' , $m , 2 ) ) ;
  304. $m = explode ( '"' , $m ) ;
  305. array_pop ( $m ) ;
  306. $m = implode ( '"' , $m ) ;
  307. if ( in_array ( $m , $ignore_categories ) ) continue ;
  308. if ( $m == 'Quality images' ) {
  309. $titles['qualityimage'] = 1 ; // Just to make sure...
  310. continue ;
  311. }
  312. if ( substr ( $m , 0 , 19 ) == 'Pictures of the day' ) {
  313. if ( !isset ( $titles['potd'] ) ) $titles['potd'] = trim ( substr ( $m , 21 , 4 ) ) . "0000" ;
  314. continue ;
  315. }
  316. if ( $m == 'Self-published work' ) {
  317. $self_made = ' selfmade="1"' ;
  318. continue ;
  319. }
  320. $cats[$m] = $m ;
  321. }
  322. ksort ( $cats ) ;
  323. // Licenses (extracted from categories)
  324. $licenses = array () ;
  325. foreach ( $cats AS $cat ) {
  326. $c = strtolower ( $cat ) ;
  327. $lic = false ;
  328. if ( substr ( $c , 0 , 2 ) == 'pd' ) $lic = true ;
  329. else if ( substr ( $c , 0 , 4 ) == 'gfdl' ) $lic = true ;
  330. else if ( substr ( $c , 0 , 3 ) == 'cc-' ) $lic = true ;
  331. else if ( isset ( $license_data[$cat] ) ) $lic = true ;
  332. if ( $lic && substr ( $c , -5 ) == '-self' ) {
  333. $self_made = ' selfmade="1"' ;
  334. $cat = substr ( $cat , 0 , -5 ) ;
  335. }
  336. if ( $lic ) {
  337. $licenses[$cat] = $cat ;
  338. unset ( $cats[$cat] ) ;
  339. }
  340. }
  341. // Location
  342. $location = '' ;
  343. $lat = '' ;
  344. $lon = '' ;
  345. $matches = array () ;
  346. preg_match_all ( '/<span\s+class="latitude">([0-9\.]+)<\/span>([^<]*)/' , $text , $matches ) ;
  347. if ( count ( $matches ) == 3 ) {
  348. $lat = $matches[1][0] . $matches[2][0] ;
  349. $matches = array () ;
  350. preg_match_all ( '/<span\s+class="longitude">([0-9\.]+)<\/span>([^<]*)/' , $text , $matches ) ;
  351. if ( count ( $matches ) == 3 ) {
  352. $lon = $matches[1][0] . $matches[2][0] ;
  353. }
  354. }
  355. if ( $lat . $lon == '' ) {
  356. $matches = array () ;
  357. preg_match_all ( '/<span\s+class="geo"[^>]*>([\-0-9\.]+)\s*;\s*([\-0-9\.]+)<\/span>/' , $text , $matches ) ;
  358. if ( count ( $matches ) == 3 ) {
  359. $lat = $matches[1][0] ;
  360. $lon = $matches[2][0] ;
  361. }
  362. }
  363. if ( $lat != '' && $lon != '' ) $location = '<location><lat>' . $lat . '</lat><lon>' . $lon . '</lon></location>' ;
  364. // Fixes
  365. $justbools = array ( 'featuredpicture' , 'qualityimage' ) ;
  366. foreach ( $justbools AS $b ) {
  367. if ( isset ( $titles[$b] ) ) $titles[$b] = '1' ;
  368. }
  369. // Now print this!
  370. header('Content-type: text/xml; charset=utf-8');
  371. print '<?xml version="1.0" encoding="UTF-8"?>' ;
  372. print '<response version="' . $api_version . '">' ;
  373. print '<file>' ;
  374. print '<name>' . str_replace ( '_' , ' ' , myurlencode ( $img ) ) . '</name>' ;
  375. print '<title>' . myurlencode ( "Image:$img" ) . '</title>' ;
  376. print '<urls>' ;
  377. print '<file>' . $file_url . '</file>' ;
  378. print '<description>' . myurlencode ( $url ) . '</description>' ;
  379. if ( $thumb_url != '' ) print '<thumbnail>' . myurlencode ( $thumb_url ) . '</thumbnail>' ;
  380. print '</urls>' ;
  381. print_tag ( 'size' , $filedata[0]['size'] ) ;
  382. print_tag ( 'width' , $filedata[0]['width'] ) ;
  383. print_tag ( 'height' , $filedata[0]['height'] ) ;
  384. print_tag ( 'uploader' , $filedata[0]['user'] ) ;
  385. print_tag ( 'upload_date' , $filedata[0]['timestamp'] ) ;
  386. print_tag ( 'sha1' , $filedata[0]['sha1'] ) ;
  387. print $location ;
  388. foreach ( $titles AS $k => $v ) {
  389. if ( $k == 'potd' ) {
  390. $k = 'pictureoftheday' ;
  391. $v = substr ( $v , 0 , 4 ) . '-' . substr ( $v , 4 , 2 ) . '-' . substr ( $v , 6 , 2 ) ;
  392. }
  393. print '<' . $k . '>' . myurlencode ( $v ) . '</' . $k . '>' ;
  394. }
  395. print '</file>' ;
  396. if ( count ( $metadata ) > 0 and $meta ) {
  397. print '<meta>' ;
  398. foreach ( $metadata AS $k => $v ) {
  399. $k = strtolower ( $k ) ;
  400. print '<' . $k . '>' . myurlencode ( $v ) . '</' . $k . '>' ;
  401. }
  402. print '</meta>' ;
  403. }
  404. print '<description>' ;
  405. foreach ( $desc AS $k => $v ) {
  406. print '<language code="' . $k . '"' ;
  407. if ( isset ( $language_names[$k] ) ) {
  408. print ' name="' . myurlencode ( $language_names[$k] ) . '"' ;
  409. }
  410. print '>' . myurlencode ( $v ) . '</language>' ;
  411. }
  412. print '</description>' ;
  413. print '<categories>' ;
  414. foreach ( $cats AS $v ) {
  415. print '<category>' . myurlencode ( $v ) . '</category>' ;
  416. }
  417. print '</categories>' ;
  418. print '<licenses' . $self_made . '>' ;
  419. foreach ( $licenses AS $l ) {
  420. print '<license>' ;
  421. print '<name>' . myurlencode ( $l ) . '</name>' ;
  422. if ( isset ( $license_data[$l] ) ) {
  423. foreach ( $license_data[$l] AS $k => $v ) {
  424. print '<' . $k . '>' . myurlencode ( $v ) . '</' . $k . '>' ;
  425. }
  426. }
  427. print '</license>' ;
  428. }
  429. print '</licenses>' ;
  430. if ( $versions ) {
  431. print '<versions>' ;
  432. foreach ( $filedata AS $fd ) {
  433. print '<version>' ;
  434. foreach ( $fd AS $k => $v ) {
  435. if ( $k == 'metadata' ) continue ;
  436. print '<' . $k . '>' . myurlencode ( $v ) . '</' . $k . '>' ;
  437. }
  438. print '</version>' ;
  439. }
  440. print '</versions>' ;
  441. }
  442. print '</response>' ;
  443. ?>