"Правильный" парсинг CSV файлов

11 октября 2010
Просмотров: 17826Обсудить
Рубрика: Программирование -> PHP

CSV, пожалуй, самый популярный формат для импорта\экспорта данных для большинства CMS.

Но, к сожалению, разбор этого формата сводиться лишь к explode(";",$str);

Т.е. алгоритм такой:

- Забрали содержимое файла, разбили на строки

- Каждую строку разделили по делимитеру

- Обработали полученные данные

И еще хорошо, если файл не сразу считывается в память, а срока за строкой (бо видел и такое: $lines = file($fp);, где $fp - крохотный файлик размером в несколько десятков Мб)

Так поступал я, раньше =)

Первые грабли ожидали, когда в содержимом файла встречались ";" как часть данных. Попытавшись разбить такую строку на данные, мы, естественно, получали немного больше, чем на надо было, со сдвигами и прочей ерундой. Данные потеряны, база захламлена, месяц без премий.. кому такое надо? Выход - написать макрос для экселя, который будет заменять ";" на ",". Для этих целей даже в MS Visual Basic  пришлось разбираться.

Туда же пошли и переносы строк, встречающиеся в данных.

Сегодня же хочу вам представить самописный класс для "правильного" разбора SCV  файла.

Класс все так же построчно считывает данные для разбора, но теперь он забирает не одну строку, а целый record (логическую строку, как мы ее видем в excel).

Ограничение по использованию всего одно - валидный CSV файл на вход ;)

Т.е. строки - заключаются в " (двойные кавычки), если внутри строки используются двойные кавычки, они заменяются на "" (две двойных).

Более подробно о CSVна Википедии.

Пример использования:

<?php
if (isset($_FILES['csv_file'])) {
    if ($_FILES['csv_file']['type'] != "text/csv") {
        echo "File type must be <b>text/csv</b> but ".$_FILES['csv_file']['type']." given";
    } elseif($_FILES['csv_file']['error'] > 0) {
        echo "Error. Code: ".$_FILES['csv_file']['error'];
    }else {
        
        require_once Path.'ycsvParser.class.php';
        $ycsv = new ycsvParser($_FILES['csv_file']['tmp_name'],true);
        if (!$ycsv)
            die("Cannot start a parser");
            
        echo "<table border=1 width=100%>";
        if (count($ycsv->config)) {
            echo "<tr>";
            foreach($ycsv->config as $title) {
                echo "<td>$title</td>";
            }
            echo "</tr>";
        }
        
        while ($record = $ycsv->getRecord()) {
            $res = $ycsv->parseRecord($record);
            if (count($ycsv->config) && count($res) != count($ycsv->config)) {
                echo ("Config has <b>".count($ycsv->config)."</b> fields,<b>".count($res)."</b> given!");
            } else {
                if (count($res)) {
                    echo "<tr>";
                    foreach($res as $title) {
                        echo "<td>$title</td>";
                    }
                    echo "</tr>";
                }
            }        
    
        }
        echo "</table>";
        
        $ycsv->close();
    }    
}
?>

Некоторые пояснения, бо комментариев в примере мало

Конструктор принимает 4 параметра

1) Путь к файлу

2) Флаг, является ли первая строка конфигурацией (аля название полей в базе)

3,4) разделители

Если 2) установлен в true -  первая строка обработается как конфигурация и в данные не попадет.

ну и еще 2 метода

getRecord - возвращает строку с данными

parseRecord - разбирает эту строку и возвращает массив с данными

Тестировал работу на достаточно большом файле (16 Мб, 12к+ записей) - достаточно шустро отработал.

Архив с классом и примером можно скачать (87).

Коменты и замечания приветствуются.

зы: надеюсь кому-то будет полезным ;)

google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru
Метки: , ,


Комментариев: 7
Подписаться на комментарии по RSS
  • #12011-09-22 в 16:34:04Влад (анонимно)

    Отличный парсер, только вот пустые строки в кавычках не ловит и если они дублируются как при выгрузке из 1С, например

    Я там подправил ч-ч, чтобы работало, лежит тут

    http://narod.ru/disk/25975475001/ycsv.php.html


  • #22011-09-22 в 16:45:40yaap

    @Влад (анонимно)

    А можно пример небольшой такого файла, в котором пустые строки в кавычках ?? grin


  • #32011-10-19 в 10:20:19Maxim (анонимно)

    Спасибо! Очень выручило.


  • #42011-11-03 в 13:38:10Bogdan (анонимно)

    Первые грабли ожидали, когда в содержимом файла встречались ";" как часть данных. Попытавшись разбить такую строку на данные, мы, естественно, получали немного больше, чем на надо было, со сдвигами и прочей ерундой. Данные потеряны, база захламлена, месяц без премий.. кому такое надо? Выход - написать макрос для экселя, который будет заменять ";" на ",". Для этих целей даже в MS Visual Basic пришлось разбираться.

    Улыбнуло grin Точка с запятой специально для русской версии применяется, т.к. запятая - разделитель разрядов. Вы громадный костыль придумали. Нужно было сразу нормальный парсер сделать


  • #52011-11-03 в 13:42:53yaap

    @Bogdan (анонимно)

    Выбор пал на макросы не только из-за ;. Макросы чистили ненужные строки, удаляли кривые позиции и т.д., т.е. полностью подгоняли файл под нужный для импорта формат!


  • #62013-09-29 в 15:27:16Виталий (анонимно)

    Здравствуйте,подскажите пожалуйста как данные csv забить в бд?


  • #72013-09-30 в 11:36:35yaap

    @Виталий (анонимно)

    Как-то так:

    LOAD DATA INFILE "/var/www/full_path.to_file.csv" INTO TABLE my_table FIELDS TERMINATED BY \',\' (id, dt,i,low)


Оставьте комментарий!

Не регистрировать/аноним

Используйте нормальные имена. Ваш комментарий будет опубликован после проверки.

Если вы уже зарегистрированы как комментатор или хотите зарегистрироваться, укажите пароль и свой действующий email.
(При регистрации на указанный адрес придет письмо с кодом активации и ссылкой на ваш персональный аккаунт, где вы сможете изменить свои данные, включая адрес сайта, ник, описание, контакты и т.д.)



grin LOL cheese smile wink smirk rolleyes confused surprised big surprise tongue laugh tongue rolleye tongue wink raspberry blank stare long face ohh grrr gulp oh oh downer red face sick shut eye hmmm mad angry zipper kiss shock cool smile cool smirk cool grin cool hmm cool mad cool cheese vampire snake excaim question

(обязательно)