2010-09-25 16:09:09 UTC
Возобновляю раздел примеров кода и других решений, а то уж больно долго продолжался анабиоз. Однако, примеры будут не про парсер или про верстку, а вообще из разных областей программирования, но больше с низкоуровневым уклоном (Си, C++, С#).
Итак, начинаем. Этот пример, по сути, является перепечаткой моего аналогичного поста из ЖЖ про обход каталогов на Си.
В хэш калькуляторах (есть MD4, MD5, SHA1, SHA256, SHA384, SHA512 и Whirlpool), которые можно сгрузить с этого сайта есть функция обхода каталогов с фильтрацией (исключающей и включающей). Так вот этим кодом я решил и поделиться. Код достаточно независимый от контекста, может кому и пригодится. Да, замечание — там используется Apache Portable Runtime. Это отличная и легковесная библиотека, написанная целиком на Си.
Функционал простой. TraverseDirectory
обходит каталоги, с возможностью рекурсивного обхода и фильтрации. Для каждого файла вызывается обработчик, указатель на который задается в структуре TraverseContext
(определена в строке 21). Можно задавать несколько шаблонов фильтрации разделяя их точкой с запятой. Обратите внимание сколько много кода, на каком-нибудь C# кода будет раз в пять меньше, для того же самого :). Итак сам код:
#include <stdio.h> #include "apr_pools.h" #include "apr_strings.h" #include "apr_file_io.h" #include "apr_fnmatch.h" #include "apr_tables.h" #define ERROR_BUFFER_SIZE 2 * 1024 #define PATTERN_SEPARATOR ";" #define COMPOSITE_PATTERN_INIT_SZ 8 // composite pattern array init size #define SUBDIRS_ARRAY_INIT_SZ 16 // subdirectories array init size /*! * Structure to store file handler information. */ typedef struct DataContext { // context defs... } DataContext; typedef struct TraverseContext { int IsScanDirRecursively; apr_array_header_t* ExcludePattern; apr_array_header_t* IncludePattern; apr_status_t (* PfnFileHandler)(const char* pathToFile, void* ctx, apr_pool_t* pool); void* DataCtx; } TraverseContext; /*! * \brief Compile composite pattern into patterns' table. * PATTERN: Backslash followed by any character, including another * backslash.<br/> * MATCHES: That character exactly. * * <p> * PATTERN: ?<br/> * MATCHES: Any single character. * </p> * * <p> * PATTERN: *<br/> * MATCHES: Any sequence of zero or more characters. (Note that multiple * *s in a row are equivalent to one.) * * PATTERN: Any character other than \?*[ or a \ at the end of the pattern<br/> * MATCHES: That character exactly. (Case sensitive.) * * PATTERN: [ followed by a class description followed by ]<br/> * MATCHES: A single character described by the class description. * (Never matches, if the class description reaches until the * end of the string without a ].) If the first character of * the class description is ^ or !, the sense of the description * is reversed. The rest of the class description is a list of * single characters or pairs of characters separated by -. Any * of those characters can have a backslash in front of them, * which is ignored; this lets you use the characters ] and - * in the character class, as well as ^ and ! at the * beginning. The pattern matches a single character if it * is one of the listed characters or falls into one of the * listed ranges (inclusive, case sensitive). Ranges with * the first character larger than the second are legal but * never match. Edge cases: [] never matches, and [^] and [!] * always match without consuming a character. * * Note that these patterns attempt to match the entire string, not * just find a substring matching the pattern. * * \param pattern The pattern to match to * \param newpattern The patterns' array * \param pool Apache pool */ void CompilePattern(const char* pattern, apr_array_header_t** newpattern, apr_pool_t* pool) { char* parts = NULL; char* last = NULL; char* p = NULL; if (!pattern) { return; // important } *newpattern = apr_array_make(pool, COMPOSITE_PATTERN_INIT_SZ, sizeof(const char*)); parts = apr_pstrdup(pool, pattern); /* strtok wants non-const data */ p = apr_strtok(parts, PATTERN_SEPARATOR, &last); while (p) { *(const char**)apr_array_push(*newpattern) = p; p = apr_strtok(NULL, PATTERN_SEPARATOR, &last); } } /*! * \brief Try to match the string to the given pattern using apr_fnmatch function. * Matching is case insensitive * \param str The string we are trying to match * \param pattern The pattern to match to * \return non-zero if the string matches to the pattern specified */ int MatchToCompositePattern(const char* str, apr_array_header_t* pattern) { int i = 0; if (!pattern) { return TRUE; // important } if (!str) { return FALSE; // important } for (; i < pattern->nelts; ++i) { const char* p = ((const char**)pattern->elts)[i]; if (apr_fnmatch(p, str, APR_FNM_CASE_BLIND) == APR_SUCCESS) { return TRUE; } } return FALSE; } void PrintError(apr_status_t status) { char errbuf[ERROR_BUFFER_SIZE]; apr_strerror(status, errbuf, ERROR_BUFFER_SIZE); printf("%s\n", errbuf); } void TraverseDirectory(const char* dir, TraverseContext* ctx, apr_pool_t* pool) { apr_finfo_t info = { 0 }; apr_dir_t* d = NULL; apr_status_t status = APR_SUCCESS; char* fullPath = NULL; // Full path to file or subdirectory apr_pool_t* iterPool = NULL; apr_array_header_t* subdirs = NULL; status = apr_dir_open(&d, dir, pool); if (status != APR_SUCCESS) { PrintError(status); return; } if (ctx->IsScanDirRecursively) { subdirs = apr_array_make(pool, SUBDIRS_ARRAY_INIT_SZ, sizeof(const char*)); } apr_pool_create(&iterPool, pool); for (;;) { apr_pool_clear(iterPool); // cleanup file allocated memory status = apr_dir_read(&info, APR_FINFO_NAME | APR_FINFO_MIN, d); if (APR_STATUS_IS_ENOENT(status)) { break; } if (info.name == NULL) { // to avoid access violation PrintError(status); continue; } // Subdirectory handling code if ((info.filetype == APR_DIR) && ctx->IsScanDirRecursively) { // skip current and parent dir if (((info.name[0] == '.') && (info.name[1] == '\0')) || ((info.name[0] == '.') && (info.name[1] == '.') && (info.name[2] == '\0'))) { continue; } status = apr_filepath_merge(&fullPath, dir, info.name, APR_FILEPATH_NATIVE, pool); // IMPORTANT: so as not to use strdup if (status != APR_SUCCESS) { PrintError(status); continue; } *(const char**)apr_array_push(subdirs) = fullPath; } // End subdirectory handling code if ((status != APR_SUCCESS) || (info.filetype != APR_REG)) { continue; } if (!MatchToCompositePattern(info.name, ctx->IncludePattern)) { continue; } // IMPORTANT: check pointer here otherwise the logic will fail if (ctx->ExcludePattern && MatchToCompositePattern(info.name, ctx->ExcludePattern)) { continue; } status = apr_filepath_merge(&fullPath, dir, info.name, APR_FILEPATH_NATIVE, iterPool); if (status != APR_SUCCESS) { PrintError(status); continue; } if (ctx->PfnFileHandler(fullPath, ctx->DataCtx, iterPool) != APR_SUCCESS) { continue; // or break if you want to interrupt in case of any file handling error } } status = apr_dir_close(d); if (status != APR_SUCCESS) { PrintError(status); } // scan subdirectories found if (ctx->IsScanDirRecursively) { int i = 0; for (; i < subdirs->nelts; ++i) { const char* path = ((const char**)subdirs->elts)[i]; apr_pool_clear(iterPool); TraverseDirectory(path, ctx, iterPool); } } apr_pool_destroy(iterPool); } apr_status_t FileHandler(const char* pathToFile, DataContext* ctx, apr_pool_t* pool) { // file handler implementation return APR_SUCCESS; }
Небольшое замечание: функция FileHandler
(обработчик файлов) возвращает статус операции и в коде обхода каталогов он просто игнорируется, если вам нужно заканчивать сканирование файлов каталога при первой же ошибке обработки файла, — просто измените поведение в строке 201.
Использовать код можно например так:
int main() { TraverseContext dirContext = { 0 }; DataContext dataCtx = { 0 }; apr_pool_t* pool = NULL; const char* dir = NULL; // dir to traverse const char* includePattern = NULL// set exclude pattern const char* excludePattern = NULL // set include pattern apr_app_initialize(); atexit(apr_terminate); apr_pool_create(&pool, NULL); dirContext.IsScanDirRecursively = 1 // set recursively scanning option dirContext.DataCtx = &dataCtx; dirContext.PfnFileHandler = FileHandler; CompilePattern(includePattern, &dirContext.IncludePattern, pool); CompilePattern(excludePattern, &dirContext.ExcludePattern, pool); TraverseDirectory(pool, dir, &dirContext); apr_pool_destroy(pool); return 0; }