PageRenderTime 82ms CodeModel.GetById 23ms RepoModel.GetById 6ms app.codeStats 0ms

/inc/XML/db2xml.php

https://github.com/chregu/fluxcms
PHP | 550 lines | 258 code | 60 blank | 232 comment | 56 complexity | f3449c4d980eaffeb7fd0315fe2a5358 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1
  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP version 4.0 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1997, 1998, 1999, 2000, 2001 The PHP Group |
  6. // +----------------------------------------------------------------------+
  7. // | This source file is subject to version 2.0 of the PHP license, |
  8. // | that is bundled with this package in the file LICENSE, and is |
  9. // | available at through the world-wide-web at |
  10. // | http://www.php.net/license/2_02.txt. |
  11. // | If you did not receive a copy of the PHP license and are unable to |
  12. // | obtain it through the world-wide-web, please send a note to |
  13. // | license@php.net so we can mail you a copy immediately. |
  14. // +----------------------------------------------------------------------+
  15. // | Authors: Christian Stocker <chregu@phant.ch> |
  16. // +----------------------------------------------------------------------+
  17. //
  18. // $Id$
  19. /**
  20. * This class takes a PEAR::DB-Result Object, a sql-query-string or an array
  21. * and returns a xml-representation of it.
  22. *
  23. * TODO
  24. * -encoding etc, options for header
  25. * -ERROR CHECKING
  26. *
  27. * Usage example
  28. *
  29. * include_once ("DB.php");
  30. * include_once("XML/db2xml.php");
  31. * $db = DB::connect("mysql://root@localhost/xmltest");
  32. * $db2xml = new xml_db2xml();
  33. * //the next one is only needed, if you need others than the default
  34. * $db2xml->setEncoding("ISO-8859-1","UTF-8");
  35. * $result = $db->query("select * from bands");
  36. * $xmlstring = $db2xml->getXML($result);
  37. *
  38. * or
  39. *
  40. * include_once ("DB.php");
  41. * include_once("XML/db2xml.php");
  42. * $db2xml = new xml_db2xml("mysql://root@localhost/xmltest");
  43. * $db2xml->Add("select * from bands");
  44. * $xmlstring = $db2xml->getXML();
  45. *
  46. * More documentation and a tutorial/how-to can be found at
  47. * http://php.chregu.tv/sql2xml
  48. *
  49. *
  50. * @author Christian Stocker <chregu@liip.ch>
  51. * @version $Id$
  52. * @package XML_db2xml
  53. */
  54. class XML_db2xml {
  55. /**
  56. * If joined-tables should be output nested.
  57. * Means, if you have joined two or more queries, the later
  58. * specified tables will be nested within the result of the former
  59. * table.
  60. * Works at the moment only with mysql automagically. For other RDBMS
  61. * you have to provide your table-relations by hand (see user_tableinfo)
  62. *
  63. * @var boolean
  64. * @see $user_tableinfo, doSql2Xml(), doArray2Xml();
  65. */
  66. public $nested = True;
  67. /**
  68. * Name of the tag element for resultsets
  69. *
  70. * @var string
  71. * @see insertNewResult()
  72. */
  73. public $tagNameResult = 'result';
  74. /**
  75. *
  76. * @var object PEAR::DB
  77. * @access private
  78. */
  79. public $db = Null;
  80. /**
  81. * This array is used to give the structure of your database to the class.
  82. * It's especially useful, if you don't use mysql, since other RDBMS than
  83. * mysql are not able at the moment to provide the right information about
  84. * your database structure within the query. And if you have more than 2
  85. * tables joined in the sql it's also not possible for mysql to find out
  86. * your real relations.
  87. * The parameters are the same as in fieldInfo from the PEAR::DB and some
  88. * additional ones. Here they come:
  89. * From PEAR::DB->fieldinfo:
  90. *
  91. * $tableInfo[$i]['table'] : the table, which field #$i belongs to.
  92. * for some rdbms/comples queries and with arrays, it's impossible
  93. * to find out to which table the field actually belongs. You can
  94. * specify it here more accurate. Or if you want, that one fields
  95. * belongs to another table, than it actually says (yes, there's
  96. * use for that, see the upcoming tutorial ...)
  97. *
  98. * $tableInfo[$i]['name'] : the name of field #$i. if you want another
  99. * name for the tag, than the query or your array provides, assign
  100. * it here.
  101. *
  102. * Additional info
  103. * $tableInfo['parent_key'][$table] : index of the parent key for $table.
  104. * this is the field, where the programm looks for changes, if this
  105. * field changes, it assumes, that we need a new 'rowset' in the
  106. * parent table.
  107. *
  108. * $tableInfo['parent_table'][$table]: name of the parent table for $table.
  109. *
  110. * @var array
  111. * @access private
  112. */
  113. public $user_tableInfo = array();
  114. /**
  115. * the encoding type, the input from the db has
  116. */
  117. public $encoding_from = 'ISO-8859-1';
  118. /**
  119. * the encoding type, the output in the xml should have
  120. * (note that domxml at the moment only support UTF-8, or at least it looks like)
  121. */
  122. public $encoding_to = 'UTF-8';
  123. public $use_iconv = false;
  124. public $tagname = 'tagname';
  125. public $dsn = Null;
  126. static private $contentToXmlTranslation = false;
  127. const CONTENT_APPEND_FRAGMENT = 2;
  128. const CONTENT_APPEND_DOCUMENT = 1;
  129. const CONTENT_APPEND_PLAIN = 0;
  130. /**
  131. * Constructor
  132. * The Constructor can take a Pear::DB 'data source name' (eg.
  133. * "mysql://user:passwd@localhost/dbname") and will then connect
  134. * to the DB, or a PEAR::DB object link, if you already connected
  135. * the db before.
  136. " If you provide nothing as $dsn, you only can later add stuff with
  137. * a pear::db-resultset or as an array. providing sql-strings will
  138. * not work.
  139. * the $root param is used, if you want to provide another name for your
  140. * root-tag than "root". if you give an empty string (""), there will be no
  141. * root element created here, but only when you add a resultset/array/sql-string.
  142. * And the first tag of this result is used as the root tag.
  143. *
  144. * @param mixed $dsn PEAR::DB "data source name" or object DB object
  145. * @param string $root the name of the xml-doc root element.
  146. * @access public
  147. */
  148. function __construct ($dsn = Null,$root = 'root',$Format=Null,$InputClasses=array('Sql','Dbresult','Array','Http','File','XmlObject','String')) {
  149. if (is_null ($Format))
  150. {
  151. include_once('XML/db2xml/Format.php');
  152. $FormatClass = 'XML_db2xml_Format';
  153. }
  154. else {
  155. include_once("XML/db2xml/Format/$Format.php");
  156. $FormatClass = "XML_db2xml_Format_$Format";
  157. }
  158. $this->dsn = $dsn;
  159. if (is_object($dsn) && $dsn->isUtf8) {
  160. $this->encoding_from = "UTF-8";
  161. $this->use_iconv = true;
  162. }
  163. $this->Format = new $FormatClass($this);
  164. $this->InputClasses = $InputClasses;
  165. $this->Format->setXmlDoc( new DomDocument('1.0'));
  166. if ($root) {
  167. $this->Format->setXmlRoot( $this->Format->xmldoc->appendChild($this->Format->xmldoc->createElement($root)));
  168. }
  169. }
  170. /**
  171. * General method for adding new resultsets to the xml-object
  172. * Give a sql-query-string, a pear::db_result object or an array as
  173. * input parameter, and the method calls the appropriate method for this
  174. * input and adds this to $this->xmldoc
  175. *
  176. * @param string sql-string, or object db_result, or array
  177. * @param mixed additional parameters for the following functions
  178. * @access public
  179. * @see addResult(), addSql(), addArray(), addXmlFile()
  180. */
  181. function add($resultset, $params = Null)
  182. {
  183. if (is_array($this->InputClasses ))
  184. {
  185. foreach($this->InputClasses as $TestClass)
  186. {
  187. include_once("XML/db2xml/Input/$TestClass.php");
  188. if (call_user_func("addTestBefore_$TestClass",$resultset))
  189. {
  190. return $this->addWithInput($TestClass,$resultset,$params);
  191. }
  192. }
  193. }
  194. }
  195. function addWithInput($container,$resultset, $params = Null)
  196. {
  197. $class = "XML_db2xml_Input_$container";
  198. if (!isset($this->Input) || get_class($this->Input) != strtolower($class))
  199. {
  200. include_once("XML/db2xml/Input/$container.php");
  201. $this->Input= new $class ($this);
  202. }
  203. $this->Input->add($resultset,$params);
  204. }
  205. /**
  206. * Returns an xml-string with a xml-representation of the resultsets.
  207. *
  208. * The resultset can be directly provided here, or if you need more than one
  209. * in your xml, then you have to provide each of them with add() before you
  210. * call getXML, but the last one can also be provided here.
  211. *
  212. * @param mixed $result result Object from a DB-query
  213. * @return string xml
  214. * @access public
  215. */
  216. function getXML($result = Null)
  217. {
  218. $xmldoc = $this->getXMLObject($result);
  219. return $xmldoc->saveXML();
  220. }
  221. /**
  222. * Returns an xml DomDocument Object with a xml-representation of the resultsets.
  223. *
  224. * The resultset can be directly provided here, or if you need more than one
  225. * in your xml, then you have to provide each of them with add() before you
  226. * call getXMLObject, but the last one can also be provided here.
  227. *
  228. * @param mixed $result result Object from a DB-query
  229. * @return Object DomDocument
  230. * @access public
  231. */
  232. function getXMLObject($result = Null)
  233. {
  234. if ($result) {
  235. $this->add ($result);
  236. }
  237. return $this->Format->xmldoc;
  238. }
  239. /**
  240. * This method sets the options for the class
  241. * One can only set variables, which are defined at the top of
  242. * of this class.
  243. *
  244. * @param array options to be passed to the class
  245. * @param boolean if the old suboptions should be deleted
  246. * @access public
  247. * @see $nested,$user_options,$user_tableInfo
  248. */
  249. function setOptions($options,$delete = False) {
  250. //this first if is for compatibility reasons
  251. // better use setUserOptions or $this->Format->setOptions
  252. if (isset($options['user_options']))
  253. {
  254. $this->Format->SetOptions($options);
  255. }
  256. if (is_array($options))
  257. {
  258. foreach ($options as $option => $value)
  259. {
  260. if (isset($this->{$option}))
  261. {
  262. if (is_array($value) && ! $delete)
  263. {
  264. foreach ($value as $suboption => $subvalue)
  265. {
  266. $this->{$option}["$suboption"] = $subvalue;
  267. }
  268. }
  269. else
  270. {
  271. $this->$option = $value;
  272. }
  273. }
  274. }
  275. }
  276. }
  277. function setFormatOptions($options,$delete = False) {
  278. $FormatOptions['user_options'] = $options;
  279. $this->Format->SetOptions($FormatOptions);
  280. }
  281. // here come some helper functions...
  282. /**
  283. * make utf8 out of the input data and escape & with &amp; and "< " with "&lt; "
  284. * (we assume that when there's no space after < it's a tag, which we need in the xml)
  285. * I'm not sure, if this is the standard way, but it works for me.
  286. *
  287. * @param string text to be utfed.
  288. * @access private
  289. */
  290. function xml_encode ($text, $stringReplace = null)
  291. {
  292. if ($this->use_iconv)
  293. {
  294. // Notice: iconv(): Unknown error (0) in /home/bitlib2/php/XML/db2xml.php on line 327
  295. // strange messaages as in 4.3.0-dev, turn them off
  296. $text = @iconv($this->encoding_from,$this->encoding_to,preg_replace('/\&amp;([#a-z0-9A-Z]+);/','&$1;',str_replace('&','&amp;',$text)));
  297. if ($stringReplace) {
  298. $text = str_replace($stringReplace[0],$stringReplace[1],$text);
  299. }
  300. if (! isset($text) )
  301. {
  302. if (isset($php_errormsg))
  303. {
  304. $errormsg = "error: $php_errormsg";
  305. }
  306. else
  307. {
  308. $errormsg = 'undefined iconv error, turn on track_errors in php.ini to get more details';
  309. }
  310. return PEAR::raiseError($errormsg,Null,PEAR_ERROR_DIE);
  311. }
  312. else {
  313. return $text;
  314. }
  315. }
  316. else
  317. {
  318. $ret = utf8_encode(preg_replace('/\&amp;([#a-z0-9A-Z]+);/','&$1;',str_replace('&','&amp;',$text)));
  319. if ($stringReplace) {
  320. $ret = str_replace($stringReplace[0],$stringReplace[1],$ret);
  321. }
  322. return $ret;
  323. }
  324. return $text;
  325. }
  326. //taken from kc@hireability.com at http://www.php.net/manual/en/function.array-merge-recursive.php
  327. /**
  328. * There seemed to be no built in function that would merge two arrays recursively and clobber
  329. * any existing key/value pairs. Array_Merge() is not recursive, and array_merge_recursive
  330. * seemed to give unsatisfactory results... it would append duplicate key/values.
  331. *
  332. * So here's a cross between array_merge and array_merge_recursive
  333. **/
  334. /**
  335. *
  336. * @param array first array to be merged
  337. * @param array second array to be merged
  338. * @return array merged array
  339. * @access private
  340. */
  341. function array_merge_clobber($a1,$a2)
  342. {
  343. if(!is_array($a1) || !is_array($a2)) return false;
  344. $newarray = $a1;
  345. while (list($key, $val) = each($a2))
  346. {
  347. if (is_array($val) && is_array($newarray[$key]))
  348. {
  349. $newarray[$key] = $this->array_merge_clobber($newarray[$key], $val);
  350. }
  351. else
  352. {
  353. $newarray[$key] = $val;
  354. }
  355. }
  356. return $newarray;
  357. }
  358. /**
  359. * sets the encoding for the db2xml transformation
  360. * @param string $encoding_from encoding to transform from
  361. * @param string $encoding_to encoding to transform to
  362. * @access public
  363. */
  364. function setEncoding ($encoding_from = 'ISO-8859-1', $encoding_to ='UTF-8')
  365. {
  366. if ($encoding_from == 'ISO-8859-1' && $encoding_to ='UTF-8') {
  367. $this->use_iconv = false;
  368. } else if (function_exists('iconv') && isset($this->encoding_from) && isset($this->encoding_to)) {
  369. $this->use_iconv = true;
  370. ini_set('track_errors',1);
  371. }
  372. $this->encoding_from = $encoding_from;
  373. $this->encoding_to = $encoding_to;
  374. }
  375. /**
  376. * @param array $parentTables parent to child relation
  377. * @access public
  378. */
  379. function SetParentTables($parentTables)
  380. {
  381. foreach ($parentTables as $table => $parent)
  382. {
  383. $table_info['parent_table'][$table]=$parent;
  384. }
  385. $this->SetOptions(array('user_tableInfo'=>$table_info));
  386. }
  387. /**
  388. * returns the content of the first match of the xpath expression
  389. *
  390. * @param string $expr xpath expression
  391. * @return mixed content of the evaluated xpath expression
  392. * @access public
  393. */
  394. function getXpathValue ($expr)
  395. {
  396. $xpth = new DOMXPath($this->Format->xmldoc);
  397. $xnode = $xpth->query($expr);
  398. if ($xnode->item(0))
  399. {
  400. $firstnode = $xnode->item(0);
  401. $children = $firstnode->firstChild;
  402. $value = $children->nodeValue;
  403. return $value;
  404. }
  405. else
  406. {
  407. return Null;
  408. }
  409. }
  410. /**
  411. * get the values as an array from the childtags from the first match of the xpath expression
  412. *
  413. * @param string xpath expression
  414. * @return array with key->value of subtags
  415. * @access public
  416. */
  417. function getXpathChildValues ($expr)
  418. {
  419. $xpth = new DOMXPath($this->Format->xmldoc);
  420. $xnode = $xpth->query($expr);
  421. if ($xnode->item(0))
  422. {
  423. foreach ($xnode->item(0)->childNodes as $child)
  424. {
  425. $children = $child->childNodes;
  426. if($children) {
  427. $value[$child->nodeName] = $children->item(0)->nodeValue;
  428. }
  429. }
  430. return $value;
  431. }
  432. else
  433. {
  434. return Null;
  435. }
  436. }
  437. function setNested ($nested)
  438. {
  439. $this->nested = $nested;
  440. }
  441. public function setContentIsXml($bool) {
  442. if ($bool) {
  443. // the CONTENT_APPEND_FRAGMENT should be faster than the one with DOCUMENT
  444. // please apply the patch from
  445. // http://svn.bitflux.ch/repos/public/misc/dompatches/documentfragment_appendXML.patch
  446. // if you're using sth around PHP 5.0.0
  447. if (version_compare(phpversion(),"5.0.99",">") || is_callable(array('domdocumentfragment','appendXML'))) {
  448. self::$contentToXmlTranslation = XML_db2xml::CONTENT_APPEND_FRAGMENT;
  449. } else {
  450. self::$contentToXmlTranslation = XML_db2xml::CONTENT_APPEND_DOCUMENT;
  451. }
  452. } else {
  453. self::$contentToXmlTranslation = XML_db2xml::CONTENT_APPEND_PLAIN;
  454. }
  455. }
  456. static function newChild($parent, $newChild, $content) {
  457. if ($parent->nodeType == XML_DOCUMENT_NODE) {
  458. $doc = $parent;
  459. } else {
  460. $doc = $parent->ownerDocument;
  461. }
  462. if ( self::$contentToXmlTranslation && (strpos($content,"<") !== false || preg_match("/\&\S;/", $content) )) {
  463. if (self::$contentToXmlTranslation == XML_db2xml::CONTENT_APPEND_FRAGMENT) {
  464. $f = $doc->createDocumentFragment();
  465. $f->appendXML("<$newChild>".$content."</$newChild>");
  466. return $parent->appendChild($f);
  467. } else { //self::$contentToXmlTranslation == CONTENT_APPEND_DOCUMENT in this case, no need to check
  468. //insert that code here
  469. $dom = new DomDocument();
  470. $dom->loadXML("<$newChild>".$content."</$newChild>");
  471. $newnode = $parent->appendChild($doc->importNode($dom->documentElement,true));
  472. unset ($dom);
  473. return $newnode;
  474. }
  475. }
  476. return $parent->appendChild( $doc->createElement($newChild,$content));
  477. }
  478. static function addRoot($doc,$root) {
  479. if ($doc->documentElement && $doc->documentElement->nodeName == $root) {
  480. return $doc->documentElement;
  481. }
  482. $root = $doc->createElement($root);
  483. if ($doc->documentElement) {
  484. return $doc->replaceChild($root,$doc->documentElement);
  485. } else {
  486. return $doc->appendChild($root);
  487. }
  488. }
  489. }
  490. ?>