2025-11-27 16:46:48 +09:00

665 lines
19 KiB
C++

// =============================================================================
// Class Implementation : COXFileChanger
// =============================================================================
//
// Version: 9.3
// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office. For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
// //////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "OXFileChanger.h"
#include "UTBStrOp.h"
#include "UTB64Bit.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNAMIC(COXFileChanger, CObject)
/////////////////////////////////////////////////////////////////////////////
// Definition of static members
LONG COXFileChanger::m_nBufferSize = 0xFFFF;
const int COXFileChanger::m_nFlagUseCFile = 0x0001;
// --- 00000001 1: UseFiles(CFile*); 0: UseFiles(pszFileName)
const int COXFileChanger::m_nFlagOverwrite = 0x0002;
// --- 00000010 1: Overwrite; 0: Output to another file
const int COXFileChanger::m_nFlagReplace = 0x0004;
// --- 00000100 1: Replace(); 0: Search()
const int COXFileChanger::m_nFlagText = 0x0008;
// --- 00001000 1: Text; 0: Binary
const int COXFileChanger::m_nFlagAll = 0x0010;
// --- 00010000 bAll ? 1 : 0
const int COXFileChanger::m_nFlagMatchCase = 0x0020;
// --- 00100000 bMatchCase ? 1 : 0
const int COXFileChanger::m_nFlagWholeWord = 0x0040;
// --- 01000000 bWholeWordOnly ? 1 : 0
TCHAR szTempFilePrefix[] = _T("~fc");
LPCTSTR COXFileChanger::m_sTempFilePrefix = szTempFilePrefix;
// ---- Prefix used to create temporary file
// Data members -------------------------------------------------------------
// protected:
// UINT m_nFlag;
// --- Internal status flags
// CFile* m_pFileIn;
// --- Actual input file
// CFile* m_pFileOut;
// --- Actual output file
// CFile m_fileIn;
// --- Used when file name is supplied
// CFile m_fileOut;
// --- Used when file name is supplied
// CString m_sInputFileName;
// --- Supplied input file name
// CString m_sOutputFileName;
// --- Supplied output file name
// CByteArray* m_pSearch;
// --- Search bytes
// CByteArray* m_pSearchOppositeCase;
// --- Search bytes with opposite case
// CByteArray* m_pReplace;
// --- Replace bytes
// Member functions ---------------------------------------------------------
COXFileChanger::COXFileChanger()
:
m_hNotifyWnd(NULL),
m_nFlag(0),
m_pFileIn(NULL),
m_pFileOut(NULL),
m_fileIn(),
m_fileOut(),
m_sInputFileName(),
m_sOutputFileName(),
m_pSearch(NULL),
m_pSearchOppositeCase(NULL),
m_pReplace(NULL)
{
}
COXFileChanger::~COXFileChanger()
{
}
void COXFileChanger::UseFiles(LPCTSTR pszInputFileName,
LPCTSTR pszOutputFileName /* = NULL */)
{
ASSERT(pszInputFileName);
m_pFileIn = &m_fileIn;
m_pFileOut = &m_fileOut;
m_sInputFileName = pszInputFileName;
ASSERT(!m_sInputFileName.IsEmpty());
m_sOutputFileName = pszOutputFileName;
SetFlag(m_nFlagUseCFile, FALSE);
SetFlag(m_nFlagOverwrite, (pszOutputFileName == NULL ||
m_sInputFileName.CompareNoCase(m_sOutputFileName) == 0));
}
void COXFileChanger::UseFiles(CFile* pInputFile, CFile* pOutputFile /* = NULL */)
{
ASSERT((pInputFile != NULL) && (pInputFile != pOutputFile));
m_pFileIn = pInputFile;
m_pFileOut = pOutputFile;
m_sInputFileName.Empty();
m_sOutputFileName.Empty();
SetFlag(m_nFlagUseCFile);
SetFlag(m_nFlagOverwrite, FALSE);
}
void COXFileChanger::SetNotificationWnd(CWnd* pNotifyWnd)
{
m_hNotifyWnd = pNotifyWnd->GetSafeHwnd();
}
UINT COXFileChanger::Search (LPCTSTR pszText,
BOOL bAll /* = FALSE */, BOOL bMatchCase /* = FALSE */,
BOOL bWholeWordOnly /* = FALSE */)
{
ASSERT(m_pFileIn != NULL);
CByteArray searchText, searchTextRevCase;
CopyTextToByteArray(pszText, m_pSearch = &searchText);
if (!bMatchCase)
{
CString sText = pszText;
ReverseCase(sText);
CopyTextToByteArray(sText, m_pSearchOppositeCase = &searchTextRevCase);
}
SetFlag(m_nFlagReplace, FALSE);
SetFlag(m_nFlagText);
SetFlag(m_nFlagAll, bAll);
SetFlag(m_nFlagMatchCase, bMatchCase);
SetFlag(m_nFlagWholeWord, bWholeWordOnly);
return Run();
}
UINT COXFileChanger::Search (const CByteArray& binary, BOOL bAll /* = FALSE */)
{
ASSERT(m_pFileIn != NULL);
m_pSearch = (CByteArray*)&binary;
SetFlag(m_nFlagReplace, FALSE);
SetFlag(m_nFlagText, FALSE);
SetFlag(m_nFlagAll, bAll);
return Run();
}
UINT COXFileChanger::Replace(LPCTSTR pszText, LPCTSTR pszReplaceText,
BOOL bAll /* = FALSE */, BOOL bMatchCase /* = FALSE */,
BOOL bWholeWordOnly /* = FALSE */)
{
ASSERT((m_pFileIn != NULL) && (m_pFileOut != NULL));
CByteArray searchText, searchTextRevCase, replaceText;
CopyTextToByteArray(pszText, m_pSearch = &searchText);
if (!bMatchCase)
{
CString sText = pszText;
ReverseCase(sText);
CopyTextToByteArray(sText, m_pSearchOppositeCase = &searchTextRevCase);
}
CopyTextToByteArray(pszReplaceText, m_pReplace = &replaceText);
SetFlag(m_nFlagReplace);
SetFlag(m_nFlagText);
SetFlag(m_nFlagAll, bAll);
SetFlag(m_nFlagMatchCase, bMatchCase);
SetFlag(m_nFlagWholeWord, bWholeWordOnly);
return Run();
}
UINT COXFileChanger::Replace(const CByteArray& binary, const CByteArray& replaceBinary,
BOOL bAll /* = FALSE */)
{
ASSERT((m_pFileIn != NULL) && (m_pFileOut != NULL));
m_pSearch = (CByteArray*)&binary;
m_pReplace = (CByteArray*)&replaceBinary;
SetFlag(m_nFlagReplace);
SetFlag(m_nFlagText, FALSE);
SetFlag(m_nFlagAll, bAll);
return Run();
}
#ifdef _DEBUG
void COXFileChanger::AssertValid() const
{
CObject::AssertValid();
}
void COXFileChanger::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
dc << "\nm_nBufferSize=" << m_nBufferSize;
dc << "\nm_hNotifyWnd=" << m_hNotifyWnd;
dc << "\nm_nFlag=" << m_nFlag;
dc << "\nm_pFileIn=" << m_pFileIn;
dc << "\nm_pFileOut=" << m_pFileOut;
dc << "\nm_fileIn=" << m_fileIn;
dc << "\nm_fileOut=" << m_fileOut;
dc << "\nm_sInputFileName=" << m_sInputFileName;
dc << "\nm_sOutputFileName=" << m_sOutputFileName;
dc << "\nm_pSearch=" << m_pSearch;
dc << "\nm_pSearchOppositeCase=" << m_pSearchOppositeCase;
dc << "\nm_pReplace=" << m_pReplace;
dc << "\nm_nFlagUseCFile=" << m_nFlagUseCFile;
dc << "\nm_nFlagOverwrite=" << m_nFlagOverwrite;
dc << "\nm_nFlagReplace=" << m_nFlagReplace;
dc << "\nm_nFlagText=" << m_nFlagText;
dc << "\nm_nFlagAll=" << m_nFlagAll;
dc << "\nm_nFlagMatchCase=" << m_nFlagMatchCase;
dc << "\nm_nFlagWholeWord=" << m_nFlagWholeWord;
dc << "\nm_sTempFilePrefix=" << m_sTempFilePrefix;
dc << "\n";
}
#endif //_DEBUG
// protected:
BOOL COXFileChanger::OnFound (CFile* pInputFile, LONG nInPos, BOOL& bContinue)
// --- In : pInputFile, a CFile pointer to the input file
// nInPos, the absolute position (zero-based) of the first
// character of the found part in the input file
// --- Out : bContinue, TRUE by default if bAll was TRUE, otherwise
// FALSE by default. It may be changed within the
// function to specify whether to continue searching
// --- Returns : whether this occurrence should be ignored (FALSE) or not
// (TRUE, default)
// --- Effect : This function is called when an occurrence has been found
// during the Search().
// NOTE: The current position of the file specified by
// pInputFile may be changed within this function. It
// will be automatically reset to the last search
// position after the function returns.
{
BOOL bResult = TRUE;
if (m_hNotifyWnd != NULL)
{
COXFileChangerData fileChangerData(pInputFile, nInPos, bContinue);
bResult = (BOOL)::SendMessage(m_hNotifyWnd, WM_OX_FILE_MATCH_FOUND, (WPARAM)&fileChangerData, NULL);
}
return bResult;
}
BOOL COXFileChanger::OnPreReplace (CFile* pInputFile, LONG nInPos, BOOL& bContinue)
// --- In : pInputFile, a CFile pointer to the input file
// nInPos, the absolute position (zero-based) of the first
// character of the found part in the input file
// --- Out : bContinue, TRUE by default if bAll was TRUE, otherwise
// FALSE by default. It may be changed within the
// function to specify whether to continue searching after
// the current replacement is processed
// --- Returns : whether this occurrence should be ignored (FALSE) thus
// not replaced, or not (TRUE, default)
// --- Effect : This function is called when an occurrence has been found
// during the Replace().
// NOTE: The current position of the file specified by
// pInputFile may be changed within this function. It
// will be automatically reset to the last search
// position after the function returns.
{
BOOL bResult = TRUE;
if (m_hNotifyWnd != NULL)
{
COXFileChangerData fileChangerData(pInputFile, nInPos, bContinue);
bResult = (BOOL)::SendMessage(m_hNotifyWnd, WM_OX_FILE_PRE_REPLACE, (WPARAM)&fileChangerData, NULL);
}
return bResult;
}
void COXFileChanger::OnPostReplace (CFile* pOutputFile, LONG nOutPos)
// --- In : pOutputFile, a CFile pointer to the output file
// nOutPos, the absolute position (zero-based) of the first
// character of the replaced part in the output file.
// --- Out :
// --- Returns :
// --- Effect : This function is called after the replacement has been
// taken place. The replacing contents has already been
// written to file when this is called.
// NOTE: The current position of the file specified by
// pOutputFile may be changed within this function. It
// will be automatically reset after the function returns.
// NOTE: If a previous OnPreReplace() returns FALSE,
// OnPostReplace will not be called. In this situation
// the search may still continue depending on the value
// of the out parameter bContinue in OnPreReplace().
{
if (m_hNotifyWnd != NULL)
{
BOOL bDummy = FALSE;
COXFileChangerData fileChangerData(pOutputFile, nOutPos, bDummy);
::SendMessage(m_hNotifyWnd, WM_OX_FILE_POST_REPLACE, (WPARAM)&fileChangerData, NULL);
}
}
void COXFileChanger::SetFlag(UINT nOXFCFLAG, BOOL bValue /* = TRUE */)
// --- In : nOXFCFLAG, specify which flag to set
// bValue, specify the value to set
// --- Out :
// --- Returns :
// --- Effect : set a flag to 0 or 1
{
if (bValue)
m_nFlag |= nOXFCFLAG;
else
m_nFlag &= ~nOXFCFLAG;
}
BOOL COXFileChanger::GetFlag(UINT nOXFCFLAG)
// --- In : nOXFCFLAG, specify which flag to get
// --- Out :
// --- Returns :
// --- Effect : retreive flag status
{
return m_nFlag & nOXFCFLAG;
}
UINT COXFileChanger::Run()
// --- In :
// --- Out :
// --- Returns : occurences of found match or replacement
// --- Effect : perform search/replace
{
int M = PtrToInt(m_pSearch->GetSize());
// ... search text/binary length
int M_1 = M - 1;
// ... array max subscript
int MM_1 = M + M_1;
// ... i shift value when found
int n = 0;
// ... actual bytes read from the file at one time
int i = 0;
// ... scanning position pointer of file buffer
int j = 0;
// ... scanning position pointer of search text/binary
int w = 0;
// ... write position pointer in the loaded file buffer
int t = 0;
// ... temp value
int skip[255];
// ... skip array for each byte value
LONG lPos0 = 0;
// ... file position of the current buffer[0]
LONG nCurInPos = 0;
// ... loading position of the input file
LONG nInPos = 0;
// ... match found position of the input file
LONG nCurOutPos = 0;
// ... writing position of the output file
LONG nOutPos = 0;
// ... replace text/binary position of the output file
LONG nChPos = 0;
// ... position of prev/next char for whole word match
LONG nChPosMax = 0;
// ... maximum file position of prev/next char for whole word match
UINT nResult = 0;
// ... match occurence
BYTE* pBuffer = NULL;
// ... file buffer
BOOL bEOF = FALSE;
// ... end of file
BOOL bContinue = TRUE;
// ... default bContinue value
BOOL bReplace = GetFlag(m_nFlagReplace);
// ... Replace() (TRUE) or Search() (FALSE)
BOOL bText = GetFlag(m_nFlagText);
// ... whether text search
BOOL bMatchCase = (!bText || GetFlag(m_nFlagMatchCase));
// ... whether case-insensitive
BOOL bWholeWord = (bText && GetFlag(m_nFlagWholeWord));
// ... whether whole word
char ch = '\0';
// ... prev/next char for whole word match
CString sSafeStack;
// Increases the stack size
if (M == 0)
{
TRACE0("COXFileChanger::Run(): aborted: zero length search text/binary encountered.\r\n");
return 0;
}
if (m_nBufferSize < M)
{
TRACE0("COXFileChanger::Run(): aborted: length of search text/binary exceeds upper limit.\r\n");
return 0;
}
// Initialize skip array
for (j = 0; j < 256; j++)
skip[j] = M;
for (j = 0, t = M; j < M; j++)
skip[m_pSearch->GetAt(j)] = --t;
if (!bMatchCase)
for (j = 0, t = M; j < M; j++)
skip[m_pSearchOppositeCase->GetAt(j)] = --t;
try
{
// Alllocate the buffer
pBuffer = new BYTE[m_nBufferSize];
OpenFiles();
nChPosMax = (LONG) m_pFileIn->GetLength() - 1;
while (!bEOF && (n = m_pFileIn->Read(pBuffer, m_nBufferSize)) > 0)
{
bEOF = (n < m_nBufferSize);
if (!bContinue && bReplace)
{
// Cycle until all remaining contents are copied
m_pFileOut->Write(pBuffer, n);
continue;
}
if (!bContinue || n < M)
break;
i = - M; w = 0;
while (bContinue)
{
// search one occurence
for (i += MM_1, j = M_1; 0 <= j && i < n; i--, j--)
{
while (pBuffer[i] != m_pSearch->GetAt(j) && (bMatchCase ||
pBuffer[i] != m_pSearchOppositeCase->GetAt(j)))
{
t = skip[pBuffer[i]];
i += __max(M - j, t);
if (n <= i)
{
i++;
break;
}
j = M_1;
}
}
// out of buffer boundary
if (n <= i)
{
if (bReplace)
m_pFileOut->Write(pBuffer + w, (bEOF ? n : i - M_1) - w);
if (!bEOF)
lPos0 = (LONG) m_pFileIn->Seek(i - M_1 - n, CFile::current);
break;
}
i++;
nCurInPos = (LONG) m_pFileIn->GetPosition();
nInPos = lPos0 + i;
// test wholeword match
if (bWholeWord)
{
nChPos = i - 1;
for (t = 0; t < 2; t++)
{
// look in buffer first
if (0 <= nChPos && nChPos < n)
ch = *(pBuffer + nChPos);
else
{ // look in the file
nChPos += lPos0;
if (0 <= nChPos && nChPos <= nChPosMax)
{
m_pFileIn->Seek(nChPos, CFile::begin);
m_pFileIn->Read(&ch, 1);
}
else
// beginning or the end of the file
ch = ' ';
}
if (isalnum(ch))
break;
nChPos = i + M;
}
if (t < 2)
{
m_pFileIn->Seek(nCurInPos, CFile::begin);
i -= M_1;
continue;
}
}
// found one match
bContinue = GetFlag(m_nFlagAll);
m_pFileIn->Seek(nInPos, CFile::begin);
if (bReplace)
{
if (OnPreReplace(m_pFileIn, nInPos, bContinue))
{
nResult++;
m_pFileOut->Write(pBuffer + w, i - w);
nOutPos = (LONG) m_pFileOut->GetPosition();
m_pFileOut->Write(m_pReplace->GetData(), PtrToUint(m_pReplace->GetSize()));
w = i + M;
nCurOutPos = (LONG) m_pFileOut->GetPosition();
m_pFileOut->Seek(nOutPos, CFile::begin);
OnPostReplace(m_pFileOut, nOutPos);
m_pFileOut->Seek(nCurOutPos, CFile::begin);
}
if (!bContinue)
m_pFileOut->Write(pBuffer + w, n - w);
}
else
{
if (OnFound(m_pFileIn, nInPos, bContinue))
nResult++;
}
m_pFileIn->Seek(nCurInPos, CFile::begin);
}
}
CloseFiles();
}
catch (...)
{
if (!GetFlag(m_nFlagUseCFile))
{
CString sTempFileName = m_pFileOut->GetFilePath();
m_pFileIn->Abort();
m_pFileOut->Abort();
if (bReplace && GetFlag(m_nFlagOverwrite) && !sTempFileName.IsEmpty())
::DeleteFile(sTempFileName);
}
m_pFileIn = NULL;
m_pFileOut = NULL;
delete[] pBuffer;
pBuffer = NULL;
throw;
}
// Clean up
delete[] pBuffer;
pBuffer = NULL;
m_pFileIn = NULL;
m_pFileOut = NULL;
return nResult;
}
void COXFileChanger::OpenFiles()
// --- In :
// --- Out :
// --- Returns :
// --- Effect : prepare m_pFileIn and m_pFileOut
{
if (!GetFlag(m_nFlagUseCFile))
{
m_pFileIn->Open(m_sInputFileName, CFile::modeRead | CFile::shareDenyWrite);
if (GetFlag(m_nFlagReplace))
{
CString sOutputFileName = (GetFlag(m_nFlagOverwrite) ?
GetUniqueTempName() : m_sOutputFileName);
ASSERT(!sOutputFileName.IsEmpty());
m_pFileOut->Open(sOutputFileName, CFile::modeReadWrite | CFile::modeCreate);
}
}
m_pFileIn->SeekToBegin();
}
void COXFileChanger::CloseFiles()
// --- In :
// --- Out :
// --- Returns :
// --- Effect : close m_pFileIn and m_pFileOut
{
if (!GetFlag(m_nFlagUseCFile))
{
m_pFileIn->Close();
if (GetFlag(m_nFlagReplace))
{
CString sTempFileName = m_pFileOut->GetFilePath();
m_pFileOut->Close();
if (GetFlag(m_nFlagOverwrite) && (!::DeleteFile(m_sInputFileName) ||
!::MoveFile(sTempFileName, m_sInputFileName)))
CFileException::ThrowOsError((LONG)::GetLastError(), m_sInputFileName);
}
}
}
void COXFileChanger::CopyTextToByteArray(LPCTSTR pszText, CByteArray* pBuffer)
// --- In : pszText, the text to copy
// pBuffer, the byte array to store result
// --- Out :
// --- Returns :
// --- Effect : copy a string (ANSI or UNICODE) into a byte array
// Unicode characters are converted to ansi characters
{
#ifdef _UNICODE
size_t nLen = 0;
UTBStr::wcstombs(&nLen, NULL, 0, pszText, 0);
if (nLen < 0)
{
TRACE0("COXFileChanger::CopyTextToByteArray : Conversion of Unicode to Ansi failed, using empty string\n");
nLen = 0;
}
pBuffer->SetSize(nLen);
char* pByte = (LPSTR)pBuffer->GetData();
// ... Convert Unicode to ansi
size_t t;
UTBStr::wcstombs(&t, pByte, nLen, pszText, nLen);
#else
int nLen = _tcslen(pszText);
pBuffer->SetSize(nLen);
char* pByte = (LPSTR)pBuffer->GetData();
// ... Just copy Ansi sting
::CopyMemory(pByte, pszText, nLen);
#endif // _UNICODE
}
void COXFileChanger::ReverseCase(CString& sText)
// --- In : sText, the text to reverse case
// --- Out :
// --- Returns : a string with reversed case for each character
// --- Effect : reverse every character's case in a string
{
TCHAR ch;
for (int i = 0; i < sText.GetLength(); i++)
{
ch = sText[i];
sText.SetAt(i, (_istlower(ch) ? (TCHAR)_totupper(ch) : (TCHAR)_totlower(ch)));
}
}
CString COXFileChanger::GetUniqueTempName()
{
TCHAR szTempPath[MAX_PATH];
TCHAR szTempFile[MAX_PATH];
VERIFY(::GetTempPath(MAX_PATH, szTempPath));
VERIFY(::GetTempFileName(szTempPath, m_sTempFilePrefix, 0, szTempFile));
return CString(szTempFile);
}
// end of OXFileChanger.cpp