Table.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Text_Table
  17. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * Zend_Text_Table enables developers to create tables out of characters
  23. *
  24. * @category Zend
  25. * @package Zend_Text_Table
  26. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  27. * @license http://framework.zend.com/license/new-bsd New BSD License
  28. */
  29. class Zend_Text_Table
  30. {
  31. /**
  32. * Auto seperator settings
  33. */
  34. const AUTO_SEPARATE_NONE = 0x0;
  35. const AUTO_SEPARATE_HEADER = 0x1;
  36. const AUTO_SEPARATE_FOOTER = 0x2;
  37. const AUTO_SEPARATE_ALL = 0x4;
  38. /**
  39. * Decorator used for the table borders
  40. *
  41. * @var Zend_Text_Table_Decorator_Interface
  42. */
  43. protected $_decorator = null;
  44. /**
  45. * List of all column widths
  46. *
  47. * @var array
  48. */
  49. protected $_columnWidths = null;
  50. /**
  51. * Rows of the table
  52. *
  53. * @var array
  54. */
  55. protected $_rows = array();
  56. /**
  57. * Auto separation mode
  58. *
  59. * @var integer
  60. */
  61. protected $_autoSeparate = self::AUTO_SEPARATE_ALL;
  62. /**
  63. * Padding for columns
  64. *
  65. * @var integer
  66. */
  67. protected $_padding = 0;
  68. /**
  69. * Default column aligns for rows created by appendRow(array $data)
  70. *
  71. * @var array
  72. */
  73. protected $_defaultColumnAligns = array();
  74. /**
  75. * Plugin loader for decorators
  76. *
  77. * @var string
  78. */
  79. protected $_pluginLoader = null;
  80. /**
  81. * Charset which is used for input by default
  82. *
  83. * @var string
  84. */
  85. protected static $_inputCharset = 'utf-8';
  86. /**
  87. * Charset which is used internally
  88. *
  89. * @var string
  90. */
  91. protected static $_outputCharset = 'utf-8';
  92. /**
  93. * Option keys to skip when calling setOptions()
  94. *
  95. * @var array
  96. */
  97. protected $_skipOptions = array(
  98. 'options',
  99. 'config',
  100. 'defaultColumnAlign',
  101. );
  102. /**
  103. * Create a basic table object
  104. *
  105. * @param array $columnsWidths List of all column widths
  106. * @param Zend_Config|array $options Configuration options
  107. * @throws Zend_Text_Table_Exception When no columns widths were set
  108. */
  109. public function __construct($options = null)
  110. {
  111. // Set options
  112. if (is_array($options)) {
  113. $this->setOptions($options);
  114. } else if ($options instanceof Zend_Config) {
  115. $this->setConfig($options);
  116. }
  117. // Check if column widths were set
  118. // @todo When column widths were not set, assume auto-sizing
  119. if ($this->_columnWidths === null) {
  120. #require_once 'Zend/Text/Table/Exception.php';
  121. throw new Zend_Text_Table_Exception('You must define the column widths');
  122. }
  123. // If no decorator was given, use default unicode decorator
  124. if ($this->_decorator === null) {
  125. if (self::getOutputCharset() === 'utf-8') {
  126. $this->setDecorator('unicode');
  127. } else {
  128. $this->setDecorator('ascii');
  129. }
  130. }
  131. }
  132. /**
  133. * Set options from array
  134. *
  135. * @param array $options Configuration for Zend_Text_Table
  136. * @return Zend_Text_Table
  137. */
  138. public function setOptions(array $options)
  139. {
  140. foreach ($options as $key => $value) {
  141. if (in_array(strtolower($key), $this->_skipOptions)) {
  142. continue;
  143. }
  144. $method = 'set' . ucfirst($key);
  145. if (method_exists($this, $method)) {
  146. $this->$method($value);
  147. }
  148. }
  149. return $this;
  150. }
  151. /**
  152. * Set options from config object
  153. *
  154. * @param Zend_Config $config Configuration for Zend_Text_Table
  155. * @return Zend_Text_Table
  156. */
  157. public function setConfig(Zend_Config $config)
  158. {
  159. return $this->setOptions($config->toArray());
  160. }
  161. /**
  162. * Set column widths
  163. *
  164. * @param array $columnWidths Widths of all columns
  165. * @throws Zend_Text_Table_Exception When no columns were supplied
  166. * @throws Zend_Text_Table_Exception When a column has an invalid width
  167. * @return Zend_Text_Table
  168. */
  169. public function setColumnWidths(array $columnWidths)
  170. {
  171. if (count($columnWidths) === 0) {
  172. #require_once 'Zend/Text/Table/Exception.php';
  173. throw new Zend_Text_Table_Exception('You must supply at least one column');
  174. }
  175. foreach ($columnWidths as $columnNum => $columnWidth) {
  176. if (is_int($columnWidth) === false or $columnWidth < 1) {
  177. #require_once 'Zend/Text/Table/Exception.php';
  178. throw new Zend_Text_Table_Exception('Column ' . $columnNum . ' has an invalid'
  179. . ' column width');
  180. }
  181. }
  182. $this->_columnWidths = $columnWidths;
  183. return $this;
  184. }
  185. /**
  186. * Set auto separation mode
  187. *
  188. * @param integer $autoSeparate Auto separation mode
  189. * @return Zend_Text_Table
  190. */
  191. public function setAutoSeparate($autoSeparate)
  192. {
  193. $this->_autoSeparate = (int) $autoSeparate;
  194. return $this;
  195. }
  196. /**
  197. * Set decorator
  198. *
  199. * @param Zend_Text_Table_Decorator_Interface|string $decorator Decorator to use
  200. * @return Zend_Text_Table
  201. */
  202. public function setDecorator($decorator)
  203. {
  204. if ($decorator instanceof Zend_Text_Table_Decorator_Interface) {
  205. $this->_decorator = $decorator;
  206. } else {
  207. $classname = $this->getPluginLoader()->load($decorator);
  208. $this->_decorator = new $classname;
  209. }
  210. return $this;
  211. }
  212. /**
  213. * Set the column padding
  214. *
  215. * @param integer $padding The padding for the columns
  216. * @return Zend_Text_Table
  217. */
  218. public function setPadding($padding)
  219. {
  220. $this->_padding = max(0, (int) $padding);
  221. return $this;
  222. }
  223. /**
  224. * Get the plugin loader for decorators
  225. *
  226. * @return Zend_Loader_PluginLoader
  227. */
  228. public function getPluginLoader()
  229. {
  230. if ($this->_pluginLoader === null) {
  231. $prefix = 'Zend_Text_Table_Decorator_';
  232. $pathPrefix = 'Zend/Text/Table/Decorator/';
  233. #require_once 'Zend/Loader/PluginLoader.php';
  234. $this->_pluginLoader = new Zend_Loader_PluginLoader(array($prefix => $pathPrefix));
  235. }
  236. return $this->_pluginLoader;
  237. }
  238. /**
  239. * Set default column align for rows created by appendRow(array $data)
  240. *
  241. * @param integer $columnNum
  242. * @param string $align
  243. * @return Zend_Text_Table
  244. */
  245. public function setDefaultColumnAlign($columnNum, $align)
  246. {
  247. $this->_defaultColumnAligns[$columnNum] = $align;
  248. return $this;
  249. }
  250. /**
  251. * Set the input charset for column contents
  252. *
  253. * @param string $charset
  254. */
  255. public static function setInputCharset($charset)
  256. {
  257. self::$_inputCharset = strtolower($charset);
  258. }
  259. /**
  260. * Get the input charset for column contents
  261. *
  262. * @param string $charset
  263. */
  264. public static function getInputCharset()
  265. {
  266. return self::$_inputCharset;
  267. }
  268. /**
  269. * Set the output charset for column contents
  270. *
  271. * @param string $charset
  272. */
  273. public static function setOutputCharset($charset)
  274. {
  275. self::$_outputCharset = strtolower($charset);
  276. }
  277. /**
  278. * Get the output charset for column contents
  279. *
  280. * @param string $charset
  281. */
  282. public static function getOutputCharset()
  283. {
  284. return self::$_outputCharset;
  285. }
  286. /**
  287. * Append a row to the table
  288. *
  289. * @param array|Zend_Text_Table_Row $row The row to append to the table
  290. * @throws Zend_Text_Table_Exception When $row is neither an array nor Zend_Zext_Table_Row
  291. * @throws Zend_Text_Table_Exception When a row contains too many columns
  292. * @return Zend_Text_Table
  293. */
  294. public function appendRow($row)
  295. {
  296. if (!is_array($row) && !($row instanceof Zend_Text_Table_Row)) {
  297. #require_once 'Zend/Text/Table/Exception.php';
  298. throw new Zend_Text_Table_Exception('$row must be an array or instance of Zend_Text_Table_Row');
  299. }
  300. if (is_array($row)) {
  301. if (count($row) > count($this->_columnWidths)) {
  302. #require_once 'Zend/Text/Table/Exception.php';
  303. throw new Zend_Text_Table_Exception('Row contains too many columns');
  304. }
  305. #require_once 'Zend/Text/Table/Row.php';
  306. #require_once 'Zend/Text/Table/Column.php';
  307. $data = $row;
  308. $row = new Zend_Text_Table_Row();
  309. $colNum = 0;
  310. foreach ($data as $columnData) {
  311. if (isset($this->_defaultColumnAligns[$colNum])) {
  312. $align = $this->_defaultColumnAligns[$colNum];
  313. } else {
  314. $align = null;
  315. }
  316. $row->appendColumn(new Zend_Text_Table_Column($columnData, $align));
  317. $colNum++;
  318. }
  319. }
  320. $this->_rows[] = $row;
  321. return $this;
  322. }
  323. /**
  324. * Render the table
  325. *
  326. * @throws Zend_Text_Table_Exception When no rows were added to the table
  327. * @return string
  328. */
  329. public function render()
  330. {
  331. // There should be at least one row
  332. if (count($this->_rows) === 0) {
  333. #require_once 'Zend/Text/Table/Exception.php';
  334. throw new Zend_Text_Table_Exception('No rows were added to the table yet');
  335. }
  336. // Initiate the result variable
  337. $result = '';
  338. // Count total columns
  339. $totalNumColumns = count($this->_columnWidths);
  340. // Now render all rows, starting from the first one
  341. $numRows = count($this->_rows);
  342. foreach ($this->_rows as $rowNum => $row) {
  343. // Get all column widths
  344. if (isset($columnWidths) === true) {
  345. $lastColumnWidths = $columnWidths;
  346. }
  347. $renderedRow = $row->render($this->_columnWidths, $this->_decorator, $this->_padding);
  348. $columnWidths = $row->getColumnWidths();
  349. $numColumns = count($columnWidths);
  350. // Check what we have to draw
  351. if ($rowNum === 0) {
  352. // If this is the first row, draw the table top
  353. $result .= $this->_decorator->getTopLeft();
  354. foreach ($columnWidths as $columnNum => $columnWidth) {
  355. $result .= str_repeat($this->_decorator->getHorizontal(),
  356. $columnWidth);
  357. if (($columnNum + 1) === $numColumns) {
  358. $result .= $this->_decorator->getTopRight();
  359. } else {
  360. $result .= $this->_decorator->getHorizontalDown();
  361. }
  362. }
  363. $result .= "\n";
  364. } else {
  365. // Else check if we have to draw the row separator
  366. if ($this->_autoSeparate & self::AUTO_SEPARATE_ALL) {
  367. $drawSeparator = true;
  368. } else if ($rowNum === 1 && $this->_autoSeparate & self::AUTO_SEPARATE_HEADER) {
  369. $drawSeparator = true;
  370. } else if ($rowNum === ($numRows - 1) && $this->_autoSeparate & self::AUTO_SEPARATE_FOOTER) {
  371. $drawSeparator = true;
  372. } else {
  373. $drawSeparator = false;
  374. }
  375. if ($drawSeparator) {
  376. $result .= $this->_decorator->getVerticalRight();
  377. $currentUpperColumn = 0;
  378. $currentLowerColumn = 0;
  379. $currentUpperWidth = 0;
  380. $currentLowerWidth = 0;
  381. // Loop through all column widths
  382. foreach ($this->_columnWidths as $columnNum => $columnWidth) {
  383. // Add the horizontal line
  384. $result .= str_repeat($this->_decorator->getHorizontal(),
  385. $columnWidth);
  386. // If this is the last line, break out
  387. if (($columnNum + 1) === $totalNumColumns) {
  388. break;
  389. }
  390. // Else check, which connector style has to be used
  391. $connector = 0x0;
  392. $currentUpperWidth += $columnWidth;
  393. $currentLowerWidth += $columnWidth;
  394. if ($lastColumnWidths[$currentUpperColumn] === $currentUpperWidth) {
  395. $connector |= 0x1;
  396. $currentUpperColumn += 1;
  397. $currentUpperWidth = 0;
  398. } else {
  399. $currentUpperWidth += 1;
  400. }
  401. if ($columnWidths[$currentLowerColumn] === $currentLowerWidth) {
  402. $connector |= 0x2;
  403. $currentLowerColumn += 1;
  404. $currentLowerWidth = 0;
  405. } else {
  406. $currentLowerWidth += 1;
  407. }
  408. switch ($connector) {
  409. case 0x0:
  410. $result .= $this->_decorator->getHorizontal();
  411. break;
  412. case 0x1:
  413. $result .= $this->_decorator->getHorizontalUp();
  414. break;
  415. case 0x2:
  416. $result .= $this->_decorator->getHorizontalDown();
  417. break;
  418. case 0x3:
  419. $result .= $this->_decorator->getCross();
  420. break;
  421. default:
  422. // This can never happen, but the CS tells I have to have it ...
  423. break;
  424. }
  425. }
  426. $result .= $this->_decorator->getVerticalLeft() . "\n";
  427. }
  428. }
  429. // Add the rendered row to the result
  430. $result .= $renderedRow;
  431. // If this is the last row, draw the table bottom
  432. if (($rowNum + 1) === $numRows) {
  433. $result .= $this->_decorator->getBottomLeft();
  434. foreach ($columnWidths as $columnNum => $columnWidth) {
  435. $result .= str_repeat($this->_decorator->getHorizontal(),
  436. $columnWidth);
  437. if (($columnNum + 1) === $numColumns) {
  438. $result .= $this->_decorator->getBottomRight();
  439. } else {
  440. $result .= $this->_decorator->getHorizontalUp();
  441. }
  442. }
  443. $result .= "\n";
  444. }
  445. }
  446. return $result;
  447. }
  448. /**
  449. * Magic method which returns the rendered table
  450. *
  451. * @return string
  452. */
  453. public function __toString()
  454. {
  455. try {
  456. return $this->render();
  457. } catch (Exception $e) {
  458. trigger_error($e->getMessage(), E_USER_ERROR);
  459. }
  460. }
  461. }