Мы будем удалять символ перевода строки из конца строки, возвращаемой
csvgetline, так как, если понадобится, его можно без труда вписать обратно.
С определением поля дело обстоит довольно сложно; мы попробовали собрать
воедино варианты, которые встречались в электронных таблицах и других
программах. Получилось примерно следующее.
Поле является последовательностью из нуля или более
символов. Поля разделены
запятыми. Открывающие и завершающие пробелы (пропуски) сохраняются. Поле
может быть заключено в двойные кавычки, в этом случае оно может содержать
запятые. Поля, заключенные в двойные кавычки, могут содержать символы двойных
кавычек, представляемые парой последовательных двойных кавычек, — то есть CSV
поле "х""у" определяет строку х"у. Поля могут быть
пустыми; поле, определяемое как
"", считается пустым и эквивалентно полю, определяемому двумя смежными
запятыми.
Поля нумеруются с нуля. Как быть, если пользователь запросит несуществующее
поле, вызвав csvfield(-l) или csvfield( 100000)? Мы могли бы возвращать "" (пустую
строку), поскольку это значение можно выводить или сравнивать; программам,
которые работают с различным количеством полей, не пришлось бы принимать
специальных предосторожностей на случай обращения к несуществующему полю.
Однако этот способ не предоставляет возможности отличить пустое поле от
несуществующего. Второй вариант — выводить сообщение об ошибке или даже
прерывать работу; несколько позже мы объясним, почему так делать нежелательно.
Мы решили возвращать NULL — общепринятое в С значение для несуществующей
строки.
Сокрытие деталей. Библиотека не будет накладывать никаких ограничений ни на
длину вводимой строки, ни на количество полей. Чтобы осуществить это, либо
вызывающая сторона должна предоставить память, либо вызываемая сторона (то
есть библиотека) должна ее зарезервировать. Посмотрим, как это организовано в
сходных библиотеках: при вызове функции f gets ей передается массив и
максимальный размер; если строка оказывается
больше буфера, она разбивается
на части. Для работы с CSV такое поведение абсолютно неприемлемо, поэтому
наша библиотека будет сама выделять память по мере необходимости.
Только функция csvgetline занимается управлением памятью; вне ее ничего о
методах организации памяти не известно. Лучше всего осуществлять такую
изоляцию через интерфейс функции: получается (то есть видно снаружи),
что
csvgetline читает следующую строку — вне зависимости от ее размера, csvfield(n)
возвращает указатель на байты п-го поля текущей строки, a csvnf ields возвращает
количество полей в текущей строке.
Мы должны будем наращивать память по мере появления длинных строк или
большого количества полей. Детали того, как это происходит, спрятаны в функциях
csv; никакие другие части программы
не знают, как это делается: использует ли
библиотека маленькие массивы, наращивая их при необходимости, или, наоборот,
очень большие массивы, или вообще какой-то совершенно другой подход. Точно так
же интерфейс не раскрывает и того, когда же память высвобождается.
Если пользователь вызывает только csvgetline, то нет надобности разделять строку
на поля; это
можно сделать по специальному требованию. Происходит ли
разделение полей ретиво (eager, непосредственно при чтении строки), лениво (lazy,
только когда нужно посчитать количество полей) или очень ленива (very lazy,