[c++] Сделать из строки корректное имя файла
От: tarkil Россия http://5209.copi.ru/
Дата: 25.04.06 11:21
Оценка: 2 (1)
Функция ReplaceInvalidFNChars возвращает копию строки искажённую таким образом, чтобы она могла быть использована в Windows как имя файла.

Нужна для тех случаев, когда неуместно говорить юзеру, что "файл с таким именем не может быть создан", а надо просто подобрать другое имя, максимально похожее на исходное. В нашем проекте мне она понадобилась при реализации экспорта документа в файл. Имя файла хотелось сделать похожим на номер документа: "вх.УНО2/17" превратить в "вх_УНО2_17". Потом дописать расширение и — вуаля!

Недопустимые символы заменяются на '_', причём заменяются и некоторые формально допустимые символы, такие как пробел, прямой и обратный апостроф.

Если исходная строка соответствует зарезервированому имени (aux, prn.txt и др.) возвращается имя aux!, prn!.txt и т.п. Если указан ReplaceDots, то точки в имени тоже заменяются на подчёркивания. Проверки (естественно) производятся без учёта регистра.

Если !ReplaceDots и исходная строка состоит из одних точек (".", ".." etc), к ним дописывается восклицательный знак: "..!".

Написана под однобайтовый набор символов, кому понадобится Уникод или TCHAR — меняйте сами, всё очевидно.

// .h

// Возвращает копию строки Src искажённую таким образом, чтобы она могла быть
// использована как имя файла. Недопустимые символы заменяются на '_', причём заменяются
// и некоторые формально допустимые символы, такие как пробел, прямой и обратный
// апостроф. Если указан ReplaceDots, то точки в имени заменяются на подчёркивания.
std::string ReplaceInvalidFNChars( LPCSTR Src, bool ReplaceDots );


// .cpp

#include <algorithm>
#include <string>
#include <string.h>
#include <ctype.h>

namespace
{
    inline bool EqualCharCI( char a, char b )
    {
        return toupper( a ) == toupper( b );
    }
}

std::string ReplaceInvalidFNChars( LPCSTR Src, bool ReplaceDots )
{
    std::string Result = Src;

    // Для начала проверим на недопустимые символы
    std::string::iterator i, iend = Result.end();
    for( i = Result.begin(); i != iend; ++i )
    {
        if( *i >= 0 && *i <= 127 ) // символы >=128 допустимы все
        {
            // меняем недопустимые символы на подчёркивание; пробел считаем недопустимым
            // и некоторые другие допустимые символы тоже предадим анафеме; во избежание
            if( !isdigit( *i ) && !isalpha( *i ) &&
                    !strchr( "$%-_@~!(){}^#&+=[]", *i ) && (ReplaceDots || *i != '.') )
                *i = '_';
        }
    }

    // Запрещены имена, состоящие из всех точек, к ним тоже добавим "!"
    if( !ReplaceDots )
    {
        bool AllDotsName = true;
        std::string::iterator i, iend = Result.end();
        for( i = Result.begin(); i != iend; ++i )
            if( *i != '.' )
            {
                AllDotsName = false;
                break;
            }

        if( AllDotsName )
            return Result + '!';
    }

    // Выделим фрагмент до первой точки и определим, не является ли он одним из
    // запрещённых имён (prn, lpt1 и т.п.)
    std::string S1, S2;
    if( ReplaceDots ) // точки все позаменяли, их там нету
        S1 = Result;
    else
    {
        const char* p = strchr( Result.c_str(), '.' );
        if( p )
        {
            size_t n = p - Result.c_str();
            S1 = Result.substr( 0, n );
            S2 = Result.substr( n );
        }
        else
            S1 = Result;
    }

    // Проверяем зарезервированные имена из трёх символов
    if( S1.length() == 3 )
    {
        const static char* ResvNames[] = { "aux", "con", "prn", "nul" };
        for( int rni = 0; rni < sizeofa(ResvNames); ++rni )
        {
            if( std::equal( S1.begin(), S1.end(), ResvNames[rni], EqualCharCI ) )
                return S1 + "!" + S2;
        }
    }

    // Проверяем зарезервированные имена из четырёх символов
    if( S1.length() == 4 )
    {
        const static char* ResvNames2[] = { "lpt", "com" };
        for( int rni = 0; rni < sizeofa(ResvNames2); ++rni )
        {
            if( std::equal( S1.begin(), S1.begin()+3, ResvNames2[rni], EqualCharCI ) &&
                    isdigit( S1[3] ) )
                return S1 + "!" + S2;
        }
    }

    return Result;
}
--
wbr, Peter Taran
Re: [c++] Сделать из строки корректное имя файла
От: _FRED_ Черногория
Дата: 25.04.06 11:31
Оценка:
Здравствуйте, tarkil, Вы писали:

T>Нужна для тех случаев, когда неуместно говорить юзеру, что "файл с таким именем не может быть создан", а надо просто подобрать другое имя, максимально похожее на исходное. В нашем проекте мне она понадобилась при реализации экспорта документа в файл. Имя файла хотелось сделать похожим на номер документа: "вх.УНО2/17" превратить в "вх_УНО2_17". Потом дописать расширение и — вуаля!


Не делается ещё одна важная деталь: дописывание 02, 03, 04, … (или аналог) для одноимённых документов
... << RSDN@Home 1.2.0 alpha rev. 648>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
Re[2]: [c++] Сделать из строки корректное имя файла
От: tarkil Россия http://5209.copi.ru/
Дата: 25.04.06 11:59
Оценка: 13 (2)
Здравствуйте, _FRED_, Вы писали:

_FR>Не делается ещё одна важная деталь: дописывание 02, 03, 04, … (или аналог) для одноимённых документов


Верно. Только нагружать функцию ещё и этим — ошибочно, ибо переименование нужно не всегда. Я это реализовывал как функцию копирования, которая под шумок переименовывает копируемый файл, если такой уже имеется. Если кому нужно — пожалуйста:

std::string CopyFileWithRename( const std::string SrcFileName, const std::string DstFileName )
{
    std::string ActualDstFileName = DstFileName;
    int Postfix = 0;
    bool OutputDirCreated = false;

    while(Postfix < 10000) // до 10000 тысяч имён пробуем
    {
        if( CopyFile( SrcFileName.c_str(), ActualDstFileName.c_str(), TRUE ) )
            break;
        DWORD ErrorCode = GetLastError();
        switch( ErrorCode )
        {
            case ERROR_PATH_NOT_FOUND:
                if( OutputDirCreated )
                    raise( "Отсутствует или недоступен каталог назначения" );
                if( !MakeSureDirectoryPathExists( ActualDstFileName.c_str() ) )
                    raise( "Не удалось создать каталог назначения" );
                OutputDirCreated = true;
                break;
            case ERROR_FILE_EXISTS:
            {
                char *pDot = strrchr( DstFileName.c_str(), '.' );
                char tmp[16];
                ++Postfix;
                sprintf( tmp, "(%d)", Postfix );
                if( pDot )
                {
                    int DotPos = pDot - DstFileName.c_str();
                    ActualDstFileName = DstFileName.substr( 0, DotPos ) + tmp + pDot;
                }
                else
                    ActualDstFileName = DstFileName + tmp;
            }
                break;

            default:
                raise( "Не удалось скопировать файл. Причину брать из GetLastError()" );
        }
    }

    if( Postfix >= 10000 )
        raise( "Ну слишком много там файлов с таким именем. Ничего не вышло. Низачот." );

    return ActualDstFileName;
}


CopyFileWithRename сначала пытается скопировать файл SrcFileName в DstFileName. Если такой уже есть, дописывает к имени (перед расширением) "(1)", "(2)" и т.д. — до "(9999)". Возвращает имя, в которое файл был реально скопирован. А ещё функция пытается создать каталог, в который копируется файл, если он не существует. Уберите case ERROR_PATH_NOT_FOUND, если такое поведение не нужно.

Функция raise кидает исключение с ошибкой. Формулировки ошибок и способ их выкидывания меняйте под себя, конечно.
--
wbr, Peter Taran
Re: [c++] Сделать из строки корректное имя файла
От: tarkil Россия http://5209.copi.ru/
Дата: 14.05.06 04:20
Оценка:
Случайно заметил, что в функции используется любимый мною, но (увы!) нестандартный макрос sizeofa (size of array):

#define sizeofa(a) (sizeof(a)/sizeof(a[0]))
--
wbr, Peter Taran
Re[3]: [c++] Сделать из строки корректное имя файла
От: Аноним  
Дата: 17.05.06 11:35
Оценка:
Здравствуйте, tarkil, Вы писали:



T> while(Postfix < 10000) // до 10000 тысяч имён пробуем

T> if( Postfix >= 10000 )
T> raise( "Ну слишком много там файлов с таким именем. Ничего не вышло. Низачот." );
Re[4]: [c++] Сделать из строки корректное имя файла
От: tarkil Россия http://5209.copi.ru/
Дата: 17.05.06 12:38
Оценка:
Здравствуйте, Аноним, Вы писали:

T>> while(Postfix < 10000) // до 10000 тысяч имён пробуем

T>> if( Postfix >= 10000 )
T>> raise( "Ну слишком много там файлов с таким именем. Ничего не вышло. Низачот." );

Реально маньяк. Топором машет и молчит.

Что не так-то? Константа большая? Али символически не объявлена?
--
wbr, Peter Taran
Re: [c++] Сделать из строки корректное имя файла
От: Ka3a4oK  
Дата: 21.05.06 08:48
Оценка: :)
Мне как раз понадобилось. Попробуем
... << RSDN@Home 1.1.4 stable rev. 510>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.