Дата и время публикации:
Проблема и решение
1. Суть проблемы
Формат представления табличных данных CSV, который детально описан в RFC-4180, широко используется в качестве отчета работы аппаратных-программных средств, реализующих некий рабочий или технологический процесс, результатом которого является некая совокупность текстовых и числовых значений, представленных порой различными форматами табличных данных.
Как, например, в случае с результатом работы синтаксического анализатора fawfinder [3.1], как показано в дампе 1.1
Листинг 1.1
$ flawfinder --csv --context --minlevel=4 test.c > test_c.csv && \ test -s test_c.csv && \ echo "Success."
Который в результате синтаксического анализа исходного кода test.c [3.2] создаст файл-отчет в формате CSV, требующий комплексного подхода при извлечении табличных значений данных и имеющие сложный формат представления, с использованием разделителей (Comma Separated Values) .
Суть проблемы такой нетравиальной обработки CSV файла test_c.csv является неравномерное распределение разделителей в виде запятой (,) вместе с двойными кавычками ("), окаймляющими текст и экранирующие в нем символы резделителя, как показано в (небольшом фрагменте) файла test_c.csv, в дампе 1.2
Листинг 1.2
... test.c,32,2,5,buffer,gets,"Does not check for buffer overflows (CWE-120, CWE-20)",Use fgets() instead, …. test.c,56,3,5,buffer,strncat,"Easily used incorrectly (e.g., incorrectly computing the correct maximum size to add) [MS-banned] (CWE-120)","Consider … ...
Из которого видно, что нет и не может быть никакой закономерности в распределение кавычек для экранирования текста, как и внутри него символов запятых (,). Поэтому простое извлечение с использованием SHELL команды CUT(1) уже не хватало. Впрочем, как и тратить время на погружение в дебри AWK(1) или написание небольшой консольной программки на С . Следовательно, на мой взгляд, правильным решением было обратить взор на Python
2. Решение
2.1 Модуль CSV
Таким решением стало применение файла сценария, написанным на языке Python, вместе с модулем csv [3.3], который позволил быстро решить проблему, связанную с нетривиальной обработкой CSV файла test_c.csv [3.2]
Модуль CSV реализует класс объектов, позволяющий писать и читать табличные значения данных в формате CSV. При этом сам модуль от программиста не потребует каких-либо особых знаний в использовании формата CSV, чтобы экспортировать / импортировать данные в/из CSV-файла в соответствии с принятым по умолчанию практическими всеми офисными наборами программ стилем "excel", называемым так же в различной технической литературе диалектом (dialect), или определяемый им собственноручно. Подключается модуль с используем оператора import, как показано ниже.
import csv
К тому же, модуль csv является самодостаточным, если планируется производить с табличными данными только операции открытия,чтения и записи с файлом CSV.
2.2 Диалекты обработки данных и управление ими
По умолчанию, как уже было сказано выше, обработка табулированных данных модулем csv производится с использованием установленного по умолчанию диалекта данных "excel" [3.4], как показано в листинге 2.2.1
Листинг 2.2.1
class excel(Dialect): delimiter = ',' quotechar = '"' doublequote = True skipinitialspace = False lineterminator = '\r\n' quoting = QUOTE_MINIMAL
Который, как видно из листинга класса excel имеет базовый класс Dialect, как показано в листинге 2.2.2
Листинг 2.2.2
class Dialect: # placeholders delimiter = None quotechar = None escapechar = None doublequote = None skipinitialspace = None lineterminator = None quoting = None
Кроме того, перечисленные параметры можно указывать, перечислять, отдельно в конструкторе при создании читателя (cvsreader) или писателя (csvwriter) для уточнения применяемого диалекта по ходу пьесы без переопределения определяющего его класса.
Параметры отдельно применяемые внутри диалекта понимаются как :
- delimiter определяет разделитель табличных значений данных;
- quotechar определяет начальный и конечный символ экранирования табличных текстовых значений, которые могут содержать объявленные в delimiter разделители ; по умолчанию используется символ двойных кавычек (");
- escapechar устанавливает символ экранировки в случае, когда quotechar установлен к значению None;
- skipinitialspace указывает как интерпретировать символы табуляции и пробела между словами (whitespace), которые сразу же идут вслед за разделителем. По умолчанию установлен в значение False, что означает считать первый же символ whitespace за разделителем частью извлекаемого значения табличных данных;
- ineterminator указывает последовательность символов, на которые должны заканчиваться строки; так, как показано в классе excel, в обще-приянтом диалекте ожидается, что строки оканчиваются последовательностью символов возврата корректки и перевода на другую строку "/r/n";
- quoting управляет процессом генерации экранирующих кавычек писателем, чтобы определить, к примеру, когда нужно поставить символ кавычек или разделителя;
- doublequote управляет обработкой кавычек в самих полях, т.е. если это параметр будет установлен в значение True, то встречающийся две последовательные кавычки будут интерпретироваться как одна во время чтения, но при записи будут записаны уже две кавычки.
Параметр quoting может позволять устанавливать следующие режимы экранированием полей (значений табличных данных):
- csv.QUOTE_MINIMAL устанавливает кавычки это когда требуется;
- csv.QUOTE_ALL будет всегда устанавливать кавычки вокруг поля;
- csv.QUOTE_NONNUMERIC будет устанавливать кавычки вокруг полей, не являющиеся числовыми;
- csv.QUOTE_NONE никогда не использовать кавычки;
2.3 Операция импорта (чтения)
Производится путем создания читателя (csvreader) с использованием отдельной функции [3.5], предоставляемой модулем csv, как показано в листинге 2.3.1
Листинг 2.3.1
CSV readers are created with the reader factory function: csvreader = csv.reader(iterable [, dialect='excel'] [optional keyword args])
Которая возвращает прочитанные строки из интегрируемого объекта, передаваемого в единственным своем аргументе . При этом аргументы dialect и ключевые уточняющие его параметры (optional keyword args) являются необязательными чем и воспользуемся, чтобы прочитать проблемный файл test_c.csv, как показано 2.3.2
Листинг 2.3.2
... File_col = 0 Line_col = 1 Column_col = 2 Level_col = 3 Category_col = 4 Name_col = 5 Warning_col = 6 Suggestion_col = 7 Note_col = 8 ... with open(fl_dst, newline='') as csvfile: csvreader = csv.reader(csvfile, delimiter=',') for row in csvreader: print ( '\'%s\'' % row[File_col], '\'%s\'' % row[Line_col], '\'%s\'' % row[Column_col], '\'%s\'' % row[Level_col], '\'%s\'' % row[Category_col], '\'%s\'' % row[Name_col], '\'%s\'' % row[Warning_col], '\'%s\'' % row[Suggestion_col], '...' )
В результате выполнения которого получим строки с осознанными значениями полей, перечисляемыми через запятую и окаймленные одинарными кавычками ('), как это делает встроенная функция print, фрагмент с результатом работы который показан в дампе 2.3.3
Дамп 2.3.3
... 'examples-1.1/test.c' '32' '2' '5' 'buffer' 'gets' 'Does not check for buffer overflows (CWE-120, CWE-20)' 'Use fgets() instead' ... 'examples-1.1/test.c' '56' '3' '5' 'buffer' 'strncat' 'Easily used incorrectly (e.g., incorrectly computing the correct maximum size to add) [MS-banned] (CWE-120)' 'Consider strcat_s, strlcat, snprintf, or automatically resizing strings' ... ...
При этом, как видно из только что полученного результата, проблема, связанная с нетривиальной обработкой CSV-файла test_c.csv, перестала существовать.
Также хочется обратить на выделенную жирным и применяемую программную конструкцию "with … as", которая за собой скрывает целый блок команд и является оберткой исполняемого кода, немедленно прерываемого в случае возникновения ошибки вместе с принудительным завершением выполнения остального кода файла-сценария [3.6].
2.4 Операция экспорта (записи)
Производится путем создания писателя (csvwriter) и двух методов writerow() и writerows() [3.7], которые осуществляют экспорт одной строки или несколько строк соответственно, как показано в листинге 2.4.1
Листинг 2.4.1
csvwriter = csv.writer(iterable [, dialect='excel'] [optional keyword args]) csvwriter.writerow(row) csvwriter.writerows(someiterable)
При этом, однострочную запись с writerow() можно использовать для формирвания нового CSV-файла, как показано в листинге 2.4.2
Листинг 2.4.2
... with open('test_c.new.csv', 'a+',newline='') as csvfile: csvwriter = csv.writer(csvfile, delimiter=';') csvwriter.writerow([row[File_idx],row[Line_idx],row[Column_idx],row[CWEs_idx],row[Level_idx],row[Category_idx],row[Name_idx],row[Warning_idx],...]) nerrors += 1 ...
В результате выполнения которого будет создан файл test_c.new.csv, содержащий записанные выборочно поля строк, ранее прочитанных csvreader из файла test_c.сsv. Результат операции экспорта данных показан в листинге 2.4.3
Листинг 2.4.3
... test.c;32;2;CWE-120, CWE-20;5;buffer;gets;Does not check for buffer overflows (CWE-120, CWE-20);" gets(f);" test.c;56;3;CWE-120;5;buffer;strncat;Easily used incorrectly (e.g., incorrectly computing the correct maximum size to add) [MS-banned] (CWE-120);" strncat(d,s,sizeof(d)); /* Misuse - this should be flagged as riskier. */" ...
3. Библиография
3.1 Flawfinder – cканер лексического анализа на потенциальные уязвимости исходного кода
3.2 Flawfinder. Тестовый файл исходного кода test.c
3.4 Python. The module csv. Managing Different Dialects
3.5 Python. The module csv. Reader Objects