Функция 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;
}
Здравствуйте, tarkil, Вы писали:
T>Нужна для тех случаев, когда неуместно говорить юзеру, что "файл с таким именем не может быть создан", а надо просто подобрать другое имя, максимально похожее на исходное. В нашем проекте мне она понадобилась при реализации экспорта документа в файл. Имя файла хотелось сделать похожим на номер документа: "вх.УНО2/17" превратить в "вх_УНО2_17". Потом дописать расширение и — вуаля!
Не делается ещё одна важная деталь: дописывание 02, 03, 04, … (или аналог) для одноимённых документов
... << RSDN@Home 1.2.0 alpha rev. 648>>
Now playing: «Тихо в лесу…»
Здравствуйте, _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 кидает исключение с ошибкой. Формулировки ошибок и способ их выкидывания меняйте под себя, конечно.
Случайно заметил, что в функции используется любимый мною, но (увы!) нестандартный макрос sizeofa (size of array):
#define sizeofa(a) (sizeof(a)/sizeof(a[0]))
Мне как раз понадобилось. Попробуем
... << RSDN@Home 1.1.4 stable rev. 510>>