Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
csvfile.php
1<?php
2
3namespace Bitrix\Translate\IO;
4
7
8class CsvFile
10{
11 // fields type with delimiter,
12 public const FIELDS_TYPE_FIXED_WIDTH = 'F';
13 // fields type fixed width
14 public const FIELDS_TYPE_WITH_DELIMITER = 'R';
15
16 public const ERROR_32K_FIELD_LENGTH = '32k_field_length';
17
23
24 // field delimiter
25 public const DELIMITER_TAB = "\t";
26 public const DELIMITER_ZPT = ',';
27 public const DELIMITER_SPS = ' ';
28 public const DELIMITER_TZP = ';';
29
35
36 // UTF-8 Byte-Order Mark
37 public const BOM_TYPE_UTF8 = "\xEF\xBB\xBF";
38
44
49 protected $hasBom = false;
50
51 public const LINE_DELIMITER_WIN = "\r\n";
52 public const LINE_DELIMITER_UNIX = "\r";
53
59
64 protected $widthMap = [];
65
70 protected $firstHeader = false;
71
76 private $fileSize;
81 private $currentPosition = 0;
83 private $buffer = '';
85 private $bufferPosition = 0;
87 private $bufferSize = 0;
88
89
95 public function openLoad(): bool
96 {
97 if ($this->isExists())
98 {
99 $this->open(Main\IO\FileStreamOpenMode::READ);
100
101 $this->fileSize = $this->getSize();
102 $this->checkUtf8Bom();
103 }
104
105 return $this->isExists() && $this->isReadable();
106 }
107
116 public function openWrite(string $mode = Main\IO\FileStreamOpenMode::WRITE): bool
117 {
118 $this->open($mode);
119
120 if (\is_resource($this->filePointer))
121 {
122 if ($mode === Main\IO\FileStreamOpenMode::WRITE)
123 {
124 $this->fileSize = 0;
125 if ($this->hasBom)
126 {
127 $this->fileSize = $this->write($this->bomMark);
128 }
129 }
130 else
131 {
132 $this->fileSize = $this->getSize();
133 }
134
135 return true;
136 }
137
138 return false;
139 }
140
147 public function setUtf8Bom(string $mark = self::BOM_TYPE_UTF8): self
148 {
149 $this->bomMark = $mark;
150
151 return $this;
152 }
153
159 public function hasUtf8Bom(): bool
160 {
161 return $this->hasBom;
162 }
163
171 public function prefaceWithUtf8Bom(bool $exists = true): self
172 {
173 $this->hasBom = $exists;
174
175 return $this;
176 }
177
183 protected function getStringByteLength(string $data): int
184 {
185 return \mb_strlen($data, '8bit');
186 }
187
192 public function checkUtf8Bom(): bool
193 {
194 $this->seek(0);
195 $bom = $this->read($this->getStringByteLength($this->bomMark));
196 if($bom === $this->bomMark)
197 {
198 $this->hasBom = true;
199 }
200
201 if ($this->hasBom)
202 {
203 $this->seek($this->getStringByteLength($this->bomMark));
204 }
205 else
206 {
207 $this->seek(0);
208 }
209
210 return $this->hasBom;
211 }
212
219 public function setFieldsType(string $fieldsType = self::FIELDS_TYPE_WITH_DELIMITER): self
220 {
221 $this->fieldsType =
222 ($fieldsType === self::FIELDS_TYPE_FIXED_WIDTH ? self::FIELDS_TYPE_FIXED_WIDTH : self::FIELDS_TYPE_WITH_DELIMITER);
223
224 return $this;
225 }
226
234 public function setFieldDelimiter(string $fieldDelimiter = self::DELIMITER_TZP): self
235 {
236 $this->fieldDelimiter = (\mb_strlen($fieldDelimiter) > 1 ? \mb_substr($fieldDelimiter, 0, 1) : $fieldDelimiter);
237
238 return $this;
239 }
240
248 public function setRowDelimiter(string $rowDelimiter = self::LINE_DELIMITER_WIN): self
249 {
250 $this->rowDelimiter = $rowDelimiter;
251
252 return $this;
253 }
254
261 public function setFirstHeader(bool $firstHeader = false): self
262 {
263 $this->firstHeader = $firstHeader;
264
265 return $this;
266 }
267
273 public function getFirstHeader(): bool
274 {
275 return $this->firstHeader;
276 }
277
284 public function setWidthMap(array $mapFields): self
285 {
286 $this->widthMap = [];
287 for ($i = 0, $n = \count($mapFields); $i < $n; $i++)
288 {
289 $this->widthMap[$i] = (int)$mapFields[$i];
290 }
291
292 return $this;
293 }
294
300 protected function fetchDelimiter(): ?array
301 {
302 $isInside = false;
303 $str = '';
304 $result = [];
305 while ($this->currentPosition <= $this->fileSize)
306 {
307 $ch = $this->buffer[$this->bufferPosition];
308 if ($ch === "\r" || $ch === "\n")
309 {
310 if (!$isInside)
311 {
312 while ($this->currentPosition <= $this->fileSize)
313 {
315 $ch = $this->buffer[$this->bufferPosition];
316 if ($ch !== "\r" && $ch !== "\n")
317 {
318 break;
319 }
320 }
321 if ($this->firstHeader)
322 {
323 $this->firstHeader = false;
324 $result = [];
325 $str = '';
326 continue;
327 }
328
329 $result[] = $str;
330
331 return $result;
332 }
333 }
334 elseif ($ch === "\"")
335 {
336 if (!$isInside)
337 {
338 $isInside = true;
340 continue;
341 }
342
344 if ($this->buffer[$this->bufferPosition] !== "\"")
345 {
346 $isInside = false;
347 continue;
348 }
349 }
350 elseif ($ch === $this->fieldDelimiter)
351 {
352 if (!$isInside)
353 {
354 $result[] = $str;
355 $str = '';
357 continue;
358 }
359 }
360
361 //inline "call"
362 $this->currentPosition ++;
363 $this->bufferPosition ++;
364 if ($this->bufferPosition >= $this->bufferSize)
365 {
366 $this->buffer = $this->read(1024 * 1024);
367 $this->bufferSize = $this->getStringByteLength($this->buffer);
368 $this->bufferPosition = 0;
369 }
370
371 $str .= $ch;
372 }
373
374 if ($str !== '')
375 {
376 $result[] = $str;
377 }
378
379 if ($result === [])
380 {
381 $result = null;
382 }
383
384 return $result;
385 }
386
392 protected function fetchWidth(): ?array
393 {
394 $str = '';
395 $ind = 1;
396 $jnd = 0;
397 $result = [];
398
399 while ($this->currentPosition <= $this->fileSize)
400 {
401 $ch = $this->buffer[$this->bufferPosition];
402 if ($ch === "\r" || $ch === "\n")
403 {
404 while ($this->currentPosition <= $this->fileSize)
405 {
407 $ch = $this->buffer[$this->bufferPosition];
408 if ($ch !== "\r" && $ch !== "\n")
409 {
410 break;
411 }
412 }
413 if ($this->firstHeader)
414 {
415 $this->firstHeader = false;
416 $result = [];
417 $ind = 1;
418 $str = '';
419 continue;
420 }
421
422 $result[] = $str;
423
424 return $result;
425 }
426 if ($ind === $this->widthMap[$jnd])
427 {
428 $result[] = $str. $ch;
429 $str = '';
431 $ind ++;
432 $jnd ++;
433 continue;
434 }
435
436 //inline "call"
437 $this->currentPosition ++;
438 $this->bufferPosition ++;
439 if($this->bufferPosition >= $this->bufferSize)
440 {
441 $this->buffer = $this->read( 1024 * 1024);
442 $this->bufferSize = $this->getStringByteLength($this->buffer);
443 $this->bufferPosition = 0;
444 }
445
446 $ind ++;
447 $str .= $ch;
448 }
449
450 if ($str !== '')
451 {
452 $result[] = $str;
453 }
454
455 if ($result === [])
456 {
457 $result = null;
458 }
459
460 return $result;
461 }
462
468 public function fetch(): ?array
469 {
470 if ($this->fieldsType === self::FIELDS_TYPE_WITH_DELIMITER)
471 {
472 if ($this->fieldDelimiter === '')
473 {
474 return null;
475 }
476
477 return $this->fetchDelimiter();
478 }
479
480 if (empty($this->widthMap))
481 {
482 return null;
483 }
484
485 return $this->fetchWidth();
486 }
487
493 protected function incrementCurrentPosition(): void
494 {
495 $this->currentPosition ++;
496 $this->bufferPosition ++;
497 if ($this->bufferPosition >= $this->bufferSize)
498 {
499 $this->buffer = $this->read( 1024 * 1024);
500 $this->bufferSize = $this->getStringByteLength($this->buffer);
501 $this->bufferPosition = 0;
502 }
503 }
504
510 public function moveFirst(): void
511 {
512 $this->setPos(0);
513 }
514
520 public function getPos(): int
521 {
522 return $this->currentPosition;
523 }
524
532 public function setPos(int $position = 0): void
533 {
534 if ($position <= $this->fileSize)
535 {
536 $this->currentPosition = $position;
537 }
538 else
539 {
540 $this->currentPosition = $this->fileSize;
541 }
542
543 $pos = $this->currentPosition;
544 if($this->hasBom)
545 {
546 $pos += 3;
547 }
548 $this->seek($pos);
549
550 $this->buffer = $this->read(1024 * 1024);
551
552 $this->bufferSize = $this->getStringByteLength($this->buffer);
553 $this->bufferPosition = 0;
554 }
555
563 public function put(array $fields): bool
564 {
565 $length = false;
566 $throw32KWarning = false;
567 if ($this->fieldsType == self::FIELDS_TYPE_WITH_DELIMITER)
568 {
569 $content = '';
570 for ($i = 0, $n = \count($fields); $i < $n; $i++)
571 {
572 if ($i>0)
573 {
574 $content .= $this->fieldDelimiter;
575 }
576 //$pos1 = strpos($fields[$i], $this->fieldDelimiter);
577 //$pos2 = $pos1 || strpos($fields[$i], "\"");
578 //$pos3 = $pos2 || strpos($fields[$i], "\n");
579 //$pos4 = $pos3 || strpos($fields[$i], "\r");
580 //if ($pos1 !== false || $pos2 !== false || $pos3 !== false || $pos4 !== false)
581 if ($fields[$i] === null)
582 {
583 $fields[$i] = '';
584 }
585 elseif (\preg_match("#[\"\n\r]+#".\BX_UTF_PCRE_MODIFIER, $fields[$i]))
586 {
587 $fields[$i] = \str_replace("\"", "\"\"", $fields[$i]);
588 //$fields[$i] = str_replace("\\", "\\\\", $fields[$i]);
589 }
590 $content .= "\"";
591 $content .= $fields[$i];
592 $content .= "\"";
593
594 // ms excel las limitation with total number of characters that a cell can contain 32767 characters
595 if ($throw32KWarning !== true && $this->getStringByteLength($fields[$i]) > 32767)
596 {
597 $throw32KWarning = true;
598 }
599 }
600 if ($content !== '')
601 {
602 $content .= $this->rowDelimiter;
603
604 $length = $this->write($content);
605 if ($length !== false)
606 {
607 $this->fileSize += $length;
608 }
609 }
610 }
611 // todo: $this->fieldsType == self::FIELDS_TYPE_FIXED_WIDTH
612
613 if ($throw32KWarning)
614 {
615 if (!$this->hasError(self::ERROR_32K_FIELD_LENGTH))
616 {
617 $this->addError(new Main\Error(
618 'Excel has limit when the total number of characters that a cell can contain is 32767 characters.',
619 self::ERROR_32K_FIELD_LENGTH
620 ));
621 }
622 }
623
624 return ($length !== false);
625 }
626}
seek($position)
Definition file.php:140
setRowDelimiter(string $rowDelimiter=self::LINE_DELIMITER_WIN)
Definition csvfile.php:248
setFirstHeader(bool $firstHeader=false)
Definition csvfile.php:261
prefaceWithUtf8Bom(bool $exists=true)
Definition csvfile.php:171
openWrite(string $mode=Main\IO\FileStreamOpenMode::WRITE)
Definition csvfile.php:116
setFieldsType(string $fieldsType=self::FIELDS_TYPE_WITH_DELIMITER)
Definition csvfile.php:219
setUtf8Bom(string $mark=self::BOM_TYPE_UTF8)
Definition csvfile.php:147
setWidthMap(array $mapFields)
Definition csvfile.php:284
setPos(int $position=0)
Definition csvfile.php:532
setFieldDelimiter(string $fieldDelimiter=self::DELIMITER_TZP)
Definition csvfile.php:234
getStringByteLength(string $data)
Definition csvfile.php:183
addError(Main\Error $error)
Definition error.php:22
hasError($code)
Definition error.php:146