2010-09-25 16:09:09 UTC

Возобновляю раздел примеров кода и других решений, а то уж больно долго продолжался анабиоз. Однако, примеры будут не про парсер или про верстку, а вообще из разных областей программирования, но больше с низкоуровневым уклоном (Си, C++, С#).

Итак, начинаем. Этот пример, по сути, является перепечаткой моего аналогичного поста из ЖЖ про обход каталогов на Си.

В с этого сайта есть функция обхода каталогов с фильтрацией (исключающей и включающей). Так вот этим кодом я решил и поделиться. Код достаточно независимый от контекста, может кому и пригодится. Да, замечание — там используется 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;
}
2010-09-25 16:09:09 UTC c noweb programming snippet