232 lines
5.9 KiB
C++
232 lines
5.9 KiB
C++
|
/*
|
||
|
* Message.cpp
|
||
|
* -----------
|
||
|
* Purpose: Various functions for processing song messages (allocating, reading from file...)
|
||
|
* Notes : Those functions should offer a rather high level of abstraction compared to
|
||
|
* previous ways of reading the song messages. There are still many things to do,
|
||
|
* though. Future versions of ReadMessage() could e.g. offer charset conversion
|
||
|
* and the code is not yet ready for unicode.
|
||
|
* Some functions for preparing the message text to be written to a file would
|
||
|
* also be handy.
|
||
|
* Authors: OpenMPT Devs
|
||
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
#include "Message.h"
|
||
|
#include "../common/FileReader.h"
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
// Read song message from a mapped file.
|
||
|
// [in] data: pointer to the data in memory that is going to be read
|
||
|
// [in] length: number of characters that should be read. Trailing null characters are automatically removed.
|
||
|
// [in] lineEnding: line ending formatting of the text in memory.
|
||
|
// [out] returns true on success.
|
||
|
bool SongMessage::Read(const std::byte *data, size_t length, LineEnding lineEnding)
|
||
|
{
|
||
|
const char *str = mpt::byte_cast<const char *>(data);
|
||
|
while(length != 0 && str[length - 1] == '\0')
|
||
|
{
|
||
|
// Ignore trailing null character.
|
||
|
length--;
|
||
|
}
|
||
|
|
||
|
// Simple line-ending detection algorithm. VERY simple.
|
||
|
if(lineEnding == leAutodetect)
|
||
|
{
|
||
|
size_t nCR = 0, nLF = 0, nCRLF = 0;
|
||
|
// find CRs, LFs and CRLFs
|
||
|
for(size_t i = 0; i < length; i++)
|
||
|
{
|
||
|
char c = str[i];
|
||
|
|
||
|
if(c == '\r')
|
||
|
nCR++;
|
||
|
else if(c == '\n')
|
||
|
nLF++;
|
||
|
|
||
|
if(i && str[i - 1] == '\r' && c == '\n')
|
||
|
nCRLF++;
|
||
|
}
|
||
|
// evaluate findings
|
||
|
if(nCR == nLF && nCR == nCRLF)
|
||
|
lineEnding = leCRLF;
|
||
|
else if(nCR && !nLF)
|
||
|
lineEnding = leCR;
|
||
|
else if(!nCR && nLF)
|
||
|
lineEnding = leLF;
|
||
|
else
|
||
|
lineEnding = leMixed;
|
||
|
}
|
||
|
|
||
|
size_t finalLength = 0;
|
||
|
// calculate the final amount of characters to be allocated.
|
||
|
for(size_t i = 0; i < length; i++)
|
||
|
{
|
||
|
finalLength++;
|
||
|
if(str[i] == '\r' && lineEnding == leCRLF)
|
||
|
i++; // skip the LF
|
||
|
}
|
||
|
|
||
|
clear();
|
||
|
reserve(finalLength);
|
||
|
|
||
|
for(size_t i = 0; i < length; i++)
|
||
|
{
|
||
|
char c = str[i];
|
||
|
|
||
|
switch(c)
|
||
|
{
|
||
|
case '\r':
|
||
|
if(lineEnding != leLF)
|
||
|
c = InternalLineEnding;
|
||
|
else
|
||
|
c = ' ';
|
||
|
if(lineEnding == leCRLF)
|
||
|
i++; // skip the LF
|
||
|
break;
|
||
|
case '\n':
|
||
|
if(lineEnding != leCR && lineEnding != leCRLF)
|
||
|
c = InternalLineEnding;
|
||
|
else
|
||
|
c = ' ';
|
||
|
break;
|
||
|
case '\0':
|
||
|
c = ' ';
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
push_back(c);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool SongMessage::Read(FileReader &file, const size_t length, LineEnding lineEnding)
|
||
|
{
|
||
|
FileReader::off_t readLength = std::min(static_cast<FileReader::off_t>(length), file.BytesLeft());
|
||
|
FileReader::PinnedRawDataView fileView = file.ReadPinnedRawDataView(readLength);
|
||
|
bool success = Read(fileView.data(), fileView.size(), lineEnding);
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Read comments with fixed line length from a mapped file.
|
||
|
// [in] data: pointer to the data in memory that is going to be read
|
||
|
// [in] length: number of characters that should be read, not including a possible trailing null terminator (it is automatically appended).
|
||
|
// [in] lineLength: The fixed length of a line.
|
||
|
// [in] lineEndingLength: The padding space between two fixed lines. (there could for example be a null char after every line)
|
||
|
// [out] returns true on success.
|
||
|
bool SongMessage::ReadFixedLineLength(const std::byte *data, const size_t length, const size_t lineLength, const size_t lineEndingLength)
|
||
|
{
|
||
|
if(lineLength == 0)
|
||
|
return false;
|
||
|
clear();
|
||
|
reserve(length);
|
||
|
|
||
|
size_t readPos = 0, writePos = 0;
|
||
|
while(readPos < length)
|
||
|
{
|
||
|
size_t thisLineLength = std::min(lineLength, length - readPos);
|
||
|
append(mpt::byte_cast<const char *>(data) + readPos, thisLineLength);
|
||
|
append(1, InternalLineEnding);
|
||
|
|
||
|
// Fix weird chars
|
||
|
for(size_t pos = writePos; pos < writePos + thisLineLength; pos++)
|
||
|
{
|
||
|
switch(at(pos))
|
||
|
{
|
||
|
case '\0':
|
||
|
case '\n':
|
||
|
case '\r':
|
||
|
at(pos) = ' ';
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
readPos += thisLineLength + std::min(lineEndingLength, length - readPos - thisLineLength);
|
||
|
writePos += thisLineLength + 1;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool SongMessage::ReadFixedLineLength(FileReader &file, const size_t length, const size_t lineLength, const size_t lineEndingLength)
|
||
|
{
|
||
|
FileReader::off_t readLength = std::min(static_cast<FileReader::off_t>(length), file.BytesLeft());
|
||
|
FileReader::PinnedRawDataView fileView = file.ReadPinnedRawDataView(readLength);
|
||
|
bool success = ReadFixedLineLength(fileView.data(), fileView.size(), lineLength, lineEndingLength);
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Retrieve song message.
|
||
|
// [in] lineEnding: line ending formatting of the text in memory.
|
||
|
// [out] returns formatted song message.
|
||
|
std::string SongMessage::GetFormatted(const LineEnding lineEnding) const
|
||
|
{
|
||
|
std::string comments;
|
||
|
comments.reserve(length());
|
||
|
for(auto c : *this)
|
||
|
{
|
||
|
if(c == InternalLineEnding)
|
||
|
{
|
||
|
switch(lineEnding)
|
||
|
{
|
||
|
case leCR:
|
||
|
comments.push_back('\r');
|
||
|
break;
|
||
|
case leCRLF:
|
||
|
comments.push_back('\r');
|
||
|
comments.push_back('\n');
|
||
|
break;
|
||
|
case leLF:
|
||
|
comments.push_back('\n');
|
||
|
break;
|
||
|
default:
|
||
|
comments.push_back('\r');
|
||
|
break;
|
||
|
}
|
||
|
} else
|
||
|
{
|
||
|
comments.push_back(c);
|
||
|
}
|
||
|
}
|
||
|
return comments;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool SongMessage::SetFormatted(std::string message, LineEnding lineEnding)
|
||
|
{
|
||
|
MPT_ASSERT(lineEnding == leLF || lineEnding == leCR || lineEnding == leCRLF);
|
||
|
switch(lineEnding)
|
||
|
{
|
||
|
case leLF:
|
||
|
message = mpt::String::Replace(message, "\n", std::string(1, InternalLineEnding));
|
||
|
break;
|
||
|
case leCR:
|
||
|
message = mpt::String::Replace(message, "\r", std::string(1, InternalLineEnding));
|
||
|
break;
|
||
|
case leCRLF:
|
||
|
message = mpt::String::Replace(message, "\r\n", std::string(1, InternalLineEnding));
|
||
|
break;
|
||
|
default:
|
||
|
MPT_ASSERT_NOTREACHED();
|
||
|
break;
|
||
|
}
|
||
|
if(message == *this)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
assign(std::move(message));
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|