PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Reader / Csv / Delimiter.php

Delimiter.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Reader/Csv/Delimiter.php

145 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv;
4
5 class Delimiter
6 {
7 protected const POTENTIAL_DELIMITERS = [',', ';', "\t", '|', ':', ' ', '~'];
8
9 /** @var resource */
10 protected $fileHandle;
11
12 protected string $escapeCharacter;
13
14 protected string $enclosure;
15
16 /** @var array<string, int[]> */
17 protected array $counts = [];
18
19 protected int $numberLines = 0;
20
21 protected ?string $delimiter = null;
22
23 /**
24 * @param resource $fileHandle
25 */
26 public function __construct($fileHandle, string $escapeCharacter, string $enclosure)
27 {
28 $this->fileHandle = $fileHandle;
29 $this->escapeCharacter = $escapeCharacter;
30 $this->enclosure = $enclosure;
31
32 $this->countPotentialDelimiters();
33 }
34
35 public function getDefaultDelimiter(): string
36 {
37 return self::POTENTIAL_DELIMITERS[0];
38 }
39
40 public function linesCounted(): int
41 {
42 return $this->numberLines;
43 }
44
45 protected function countPotentialDelimiters(): void
46 {
47 $this->counts = array_fill_keys(self::POTENTIAL_DELIMITERS, []);
48 $delimiterKeys = array_flip(self::POTENTIAL_DELIMITERS);
49
50 // Count how many times each of the potential delimiters appears in each line
51 $this->numberLines = 0;
52 while (($line = $this->getNextLine()) !== false && (++$this->numberLines < 1000)) {
53 $this->countDelimiterValues($line, $delimiterKeys);
54 }
55 }
56
57 /** @param array<string, int> $delimiterKeys */
58 protected function countDelimiterValues(string $line, array $delimiterKeys): void
59 {
60 $splitString = mb_str_split($line, 1, 'UTF-8');
61 $distribution = array_count_values($splitString);
62 $countLine = array_intersect_key($distribution, $delimiterKeys);
63
64 foreach (self::POTENTIAL_DELIMITERS as $delimiter) {
65 $this->counts[$delimiter][] = $countLine[$delimiter] ?? 0;
66 }
67 }
68
69 public function infer(): ?string
70 {
71 // Calculate the mean square deviations for each delimiter
72 // (ignoring delimiters that haven't been found consistently)
73 $meanSquareDeviations = [];
74 $middleIdx = (int) floor(($this->numberLines - 1) / 2);
75
76 foreach (self::POTENTIAL_DELIMITERS as $delimiter) {
77 $series = $this->counts[$delimiter];
78 sort($series);
79
80 $median = ($this->numberLines % 2)
81 ? $series[$middleIdx]
82 : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2;
83
84 if ($median === 0) {
85 continue;
86 }
87
88 $meanSquareDeviations[$delimiter] = array_reduce(
89 $series,
90 fn ($sum, $value) => $sum + ($value - $median) ** 2
91 ) / count($series);
92 }
93
94 // ... and pick the delimiter with the smallest mean square deviation
95 // (in case of ties, the order in potentialDelimiters is respected)
96 $min = INF;
97 foreach (self::POTENTIAL_DELIMITERS as $delimiter) {
98 if (!isset($meanSquareDeviations[$delimiter])) {
99 continue;
100 }
101
102 if ($meanSquareDeviations[$delimiter] < $min) {
103 $min = $meanSquareDeviations[$delimiter];
104 $this->delimiter = $delimiter;
105 }
106 }
107
108 return $this->delimiter;
109 }
110
111 /**
112 * Get the next full line from the file.
113 *
114 * @return false|string
115 */
116 public function getNextLine()
117 {
118 $line = '';
119 $enclosure = ($this->escapeCharacter === '' ? ''
120 : ('(?<!' . preg_quote($this->escapeCharacter, '/') . ')'))
121 . preg_quote($this->enclosure, '/');
122
123 do {
124 // Get the next line in the file
125 $newLine = fgets($this->fileHandle);
126
127 // Return false if there is no next line
128 if ($newLine === false) {
129 return false;
130 }
131
132 // Add the new line to the line passed in
133 $line = $line . $newLine;
134
135 // Drop everything that is enclosed to avoid counting false positives in enclosures
136 $line = (string) preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
137
138 // See if we have any enclosures left in the line
139 // if we still have an enclosure then we need to read the next line as well
140 } while (preg_match('/(' . $enclosure . ')/', $line) > 0);
141
142 return ($line !== '') ? $line : false;
143 }
144 }
145