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;
}