CsvFormatter.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @package Grav\Framework\File\Formatter
  5. *
  6. * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
  7. * @license MIT License; see LICENSE file for details.
  8. */
  9. namespace Grav\Framework\File\Formatter;
  10. use Grav\Framework\File\Interfaces\FileFormatterInterface;
  11. class CsvFormatter extends AbstractFormatter
  12. {
  13. /**
  14. * IniFormatter constructor.
  15. * @param array $config
  16. */
  17. public function __construct(array $config = [])
  18. {
  19. $config += [
  20. 'file_extension' => ['.csv', '.tsv'],
  21. 'delimiter' => ','
  22. ];
  23. parent::__construct($config);
  24. }
  25. /**
  26. * Returns delimiter used to both encode and decode CSV.
  27. *
  28. * @return string
  29. */
  30. public function getDelimiter(): string
  31. {
  32. // Call fails on bad configuration.
  33. return $this->getConfig('delimiter');
  34. }
  35. /**
  36. * {@inheritdoc}
  37. * @see FileFormatterInterface::encode()
  38. */
  39. public function encode($data, $delimiter = null): string
  40. {
  41. if (count($data) === 0) {
  42. return '';
  43. }
  44. $delimiter = $delimiter ?? $this->getDelimiter();
  45. $header = array_keys(reset($data));
  46. // Encode the field names
  47. $string = $this->encodeLine($header, $delimiter);
  48. // Encode the data
  49. foreach ($data as $row) {
  50. $string .= $this->encodeLine($row, $delimiter);
  51. }
  52. return $string;
  53. }
  54. /**
  55. * {@inheritdoc}
  56. * @see FileFormatterInterface::decode()
  57. */
  58. public function decode($data, $delimiter = null): array
  59. {
  60. $delimiter = $delimiter ?? $this->getDelimiter();
  61. $lines = preg_split('/\r\n|\r|\n/', $data);
  62. if ($lines === false) {
  63. throw new \RuntimeException('Decoding CSV failed');
  64. }
  65. // Get the field names
  66. $header = str_getcsv(array_shift($lines), $delimiter);
  67. // Allow for replacing a null string with null/empty value
  68. $null_replace = $this->getConfig('null');
  69. // Get the data
  70. $list = [];
  71. $line = null;
  72. try {
  73. foreach ($lines as $line) {
  74. if (!empty($line)) {
  75. $csv_line = str_getcsv($line, $delimiter);
  76. if ($null_replace) {
  77. array_walk($csv_line, function(&$el) use ($null_replace) {
  78. $el = str_replace($null_replace, null, $el);
  79. });
  80. }
  81. $list[] = array_combine($header, $csv_line);
  82. }
  83. }
  84. } catch (\Exception $e) {
  85. throw new \Exception('Badly formatted CSV line: ' . $line);
  86. }
  87. return $list;
  88. }
  89. protected function encodeLine(array $line, $delimiter = null): string
  90. {
  91. foreach ($line as $key => &$value) {
  92. $value = $this->escape((string)$value);
  93. }
  94. unset($value);
  95. return implode($delimiter, $line). "\n";
  96. }
  97. protected function escape(string $value)
  98. {
  99. if (preg_match('/[,"\r\n]/u', $value)) {
  100. $value = '"' . preg_replace('/"/', '""', $value) . '"';
  101. }
  102. return $value;
  103. }
  104. }