180 lines
5.7 KiB
PHP
180 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace KupShop\FeedsBundle\Formatter;
|
|
|
|
use KupShop\FeedsBundle\Dto\FeedItem;
|
|
|
|
class StreamedCSVFormatter implements StreamedFormatterInterface
|
|
{
|
|
public static function getType(): string
|
|
{
|
|
return 'csv';
|
|
}
|
|
|
|
public static function getContentType(): string
|
|
{
|
|
return 'text/csv';
|
|
}
|
|
|
|
/**
|
|
* @param \Generator<int, FeedItem> $data
|
|
*/
|
|
public function process(array $configuration, \Generator $data): void
|
|
{
|
|
$file = new \SplFileObject('php://output', 'w');
|
|
|
|
// backward compatibility with old CsvFormat
|
|
$separator = $configuration['separator'] ?? ',';
|
|
$header = $configuration['header'] ?? false;
|
|
$enclosureCustom = $configuration['enclosure-custom'] ?? false;
|
|
$enclosure = $configuration['enclosure'] ?? '"';
|
|
$escape = $configuration['escape'] ?? '\\';
|
|
|
|
if ($timestamp = $configuration['timestamp'] ?? false) {
|
|
$file->fwrite('# timestamp '.$timestamp.PHP_EOL);
|
|
}
|
|
|
|
foreach ($this->getFlattenedGenerator($configuration, $data) as $key => $item) {
|
|
if (empty($item)) {
|
|
continue;
|
|
}
|
|
|
|
// add header
|
|
if ($header && $key === 0) {
|
|
if ($enclosureCustom) {
|
|
$file->fwrite(implode($separator, array_map([$this, 'encodeValue'], array_keys($item)))."\r\n");
|
|
} else {
|
|
$file->fputcsv(array_keys($item), $separator, $enclosure, $escape);
|
|
}
|
|
}
|
|
|
|
// add data row
|
|
if ($enclosureCustom) {
|
|
$file->fwrite(implode($separator, $item)."\r\n");
|
|
} else {
|
|
$file->fputcsv($item, $separator, $enclosure, $escape);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function getFlattenedGenerator(array $configuration, \Generator $data): \Generator
|
|
{
|
|
foreach ($data as $item) {
|
|
yield $this->prepareItem(
|
|
$configuration,
|
|
$item,
|
|
$this->toFlattenArray($item->asSimpleArray())
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts multi-dimensional array to simple flatten array.
|
|
*/
|
|
private function toFlattenArray(array $data): array
|
|
{
|
|
$flattened = [];
|
|
|
|
foreach ($data as $key => $value) {
|
|
if (is_array($value)) {
|
|
$flattened[$key] = $this->formatArrayAsMultilineString($value);
|
|
} else {
|
|
$flattened[$key] = $value === null ? '' : $value;
|
|
}
|
|
}
|
|
|
|
return $flattened;
|
|
}
|
|
|
|
private function formatArrayAsMultilineString(array $array): string
|
|
{
|
|
$formatted = [];
|
|
|
|
foreach ($array as $item) {
|
|
if (is_array($item)) {
|
|
$lines = [];
|
|
foreach ($item as $value) {
|
|
$lines[] = is_array($value) ? $this->formatArrayAsMultilineString($value) : $value;
|
|
}
|
|
$formatted[] = implode("\n\n", $lines);
|
|
} else {
|
|
$formatted[] = $item;
|
|
}
|
|
}
|
|
|
|
return implode("\n", $formatted);
|
|
}
|
|
|
|
/**
|
|
* Prepares item by its configuration in attributes.
|
|
*/
|
|
private function prepareItem(array $configuration, FeedItem $original, array $item): array
|
|
{
|
|
$newItem = [];
|
|
|
|
$enclosureCustom = $configuration['enclosure-custom'] ?? false;
|
|
|
|
foreach ($item as $key => $value) {
|
|
$attributes = $original->getAttributes($key);
|
|
|
|
// replace key by `override-name` attribute
|
|
if (!empty($attributes['override-name'])) {
|
|
$key = $attributes['override-name'];
|
|
}
|
|
|
|
$forceEnclosure = $attributes['enclosure'] ?? false;
|
|
$doubleQuotesWithoutEscaping = $attributes['double-quotes-without-escaping'] ?? false;
|
|
$tripleQuotesWithoutEscaping = $attributes['triple-quotes-without-escaping'] ?? false;
|
|
|
|
if ($enclosureCustom && ($forceEnclosure || $forceEnclosure === '') && !empty($value)) {
|
|
if ($doubleQuotesWithoutEscaping || $doubleQuotesWithoutEscaping === '') {
|
|
$value = $this->doubleQuotesWithoutEscaping($value);
|
|
} elseif ($tripleQuotesWithoutEscaping || $tripleQuotesWithoutEscaping === '') {
|
|
$value = $this->tripleQuotesWithoutEscaping($value);
|
|
} else {
|
|
$value = $this->encodeValue($value);
|
|
}
|
|
}
|
|
|
|
$newItem[$key] = $value;
|
|
}
|
|
|
|
return $newItem;
|
|
}
|
|
|
|
protected function doubleQuotesWithoutEscaping(string $value): string
|
|
{
|
|
// remove any ESCAPED double quotes within string.
|
|
$value = str_replace('\\"', '"', $value);
|
|
// then replace double quotes with two double quotes
|
|
$value = str_replace('"', '""', $value);
|
|
|
|
// force wrap value in quotes and return
|
|
return '"'.$value.'"';
|
|
}
|
|
|
|
private function tripleQuotesWithoutEscaping(string $value): string
|
|
{
|
|
// remove any ESCAPED double quotes within string.
|
|
$value = str_replace('\\"', '"', $value);
|
|
// then replace double quotes with triple double quotes
|
|
$value = str_replace('"', '"""', $value);
|
|
|
|
// force wrap value in quotes and return
|
|
return '"'.$value.'"';
|
|
}
|
|
|
|
private function encodeValue(string $value): string
|
|
{
|
|
// remove any ESCAPED double quotes within string.
|
|
$value = str_replace('\\"', '"', $value);
|
|
// then force escape these same double quotes And Any UNESCAPED Ones.
|
|
$value = str_replace('"', '\"', $value);
|
|
|
|
// force wrap value in quotes and return
|
|
return '"'.$value.'"';
|
|
}
|
|
}
|