| // $Id: io_helpers.cpp,v 1.13 2002/07/02 22:13:56 t1mpy Exp $ |
| |
| // id3lib: a C++ library for creating and manipulating id3v1/v2 tags |
| // Copyright 1999, 2000 Scott Thomas Haug |
| |
| // This library is free software; you can redistribute it and/or modify it |
| // under the terms of the GNU Library General Public License as published by |
| // the Free Software Foundation; either version 2 of the License, or (at your |
| // option) any later version. |
| // |
| // This library is distributed in the hope that it will be useful, but WITHOUT |
| // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
| // License for more details. |
| // |
| // You should have received a copy of the GNU Library General Public License |
| // along with this library; if not, write to the Free Software Foundation, |
| // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| |
| // The id3lib authors encourage improvements and optimisations to be sent to |
| // the id3lib coordinator. Please see the README file for details on where to |
| // send such submissions. See the AUTHORS file for a list of people who have |
| // contributed to id3lib. See the ChangeLog file for a list of changes to |
| // id3lib. These files are distributed with id3lib at |
| // http://download.sourceforge.net/id3lib/ |
| |
| #if defined HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include "id3/io_decorators.h" //has "readers.h" "io_helpers.h" "utils.h" |
| |
| using namespace dami; |
| |
| String io::readString(ID3_Reader& reader) |
| { |
| String str; |
| while (!reader.atEnd()) |
| { |
| ID3_Reader::char_type ch = reader.readChar(); |
| if (ch == '\0') |
| { |
| break; |
| } |
| str += static_cast<char>(ch); |
| } |
| return str; |
| } |
| |
| String io::readText(ID3_Reader& reader, size_t len) |
| { |
| String str; |
| str.reserve(len); |
| const size_t SIZE = 1024; |
| ID3_Reader::char_type buf[SIZE]; |
| size_t remaining = len; |
| while (remaining > 0 && !reader.atEnd()) |
| { |
| size_t numRead = reader.readChars(buf, min(remaining, SIZE)); |
| remaining -= numRead; |
| str.append(reinterpret_cast<String::value_type *>(buf), numRead); |
| } |
| return str; |
| } |
| |
| namespace |
| { |
| bool isNull(unsigned char ch1, unsigned char ch2) |
| { |
| return ch1 == '\0' && ch2 == '\0'; |
| } |
| |
| int isBOM(unsigned char ch1, unsigned char ch2) |
| { |
| // The following is taken from the following URL: |
| // http://community.roxen.com/developers/idocs/rfc/rfc2781.html |
| /* The Unicode Standard and ISO 10646 define the character "ZERO WIDTH |
| NON-BREAKING SPACE" (0xFEFF), which is also known informally as |
| "BYTE ORDER MARK" (abbreviated "BOM"). The latter name hints at a |
| second possible usage of the character, in addition to its normal |
| use as a genuine "ZERO WIDTH NON-BREAKING SPACE" within text. This |
| usage, suggested by Unicode section 2.4 and ISO 10646 Annex F |
| (informative), is to prepend a 0xFEFF character to a stream of |
| Unicode characters as a "signature"; a receiver of such a serialized |
| stream may then use the initial character both as a hint that the |
| stream consists of Unicode characters and as a way to recognize the |
| serialization order. In serialized UTF-16 prepended with such a |
| signature, the order is big-endian if the first two octets are 0xFE |
| followed by 0xFF; if they are 0xFF followed by 0xFE, the order is |
| little-endian. Note that 0xFFFE is not a Unicode character, |
| precisely to preserve the usefulness of 0xFEFF as a byte-order |
| mark. */ |
| |
| if (ch1 == 0xFE && ch2 == 0xFF) |
| { |
| return 1; |
| } |
| else if (ch1 == 0xFF && ch2 == 0xFE) |
| { |
| return -1; |
| } |
| return 0; |
| } |
| |
| bool readTwoChars(ID3_Reader& reader, |
| ID3_Reader::char_type& ch1, |
| ID3_Reader::char_type& ch2) |
| { |
| if (reader.atEnd()) |
| { |
| return false; |
| } |
| io::ExitTrigger et(reader); |
| ch1 = reader.readChar(); |
| if (reader.atEnd()) |
| { |
| return false; |
| } |
| et.release(); |
| ch2 = reader.readChar(); |
| return true; |
| } |
| } |
| |
| String io::readUnicodeString(ID3_Reader& reader) |
| { |
| String unicode; |
| ID3_Reader::char_type ch1, ch2; |
| if (!readTwoChars(reader, ch1, ch2) || isNull(ch1, ch2)) |
| { |
| return unicode; |
| } |
| int bom = isBOM(ch1, ch2); |
| if (!bom) |
| { |
| unicode += static_cast<char>(ch1); |
| unicode += static_cast<char>(ch2); |
| } |
| while (!reader.atEnd()) |
| { |
| if (!readTwoChars(reader, ch1, ch2) || isNull(ch1, ch2)) |
| { |
| break; |
| } |
| if (bom == -1) |
| { |
| unicode += static_cast<char>(ch2); |
| unicode += static_cast<char>(ch1); |
| } |
| else |
| { |
| unicode += static_cast<char>(ch1); |
| unicode += static_cast<char>(ch2); |
| } |
| } |
| return unicode; |
| } |
| |
| String io::readUnicodeText(ID3_Reader& reader, size_t len) |
| { |
| String unicode; |
| ID3_Reader::char_type ch1, ch2; |
| if (!readTwoChars(reader, ch1, ch2)) |
| { |
| return unicode; |
| } |
| len -= 2; |
| int bom = isBOM(ch1, ch2); |
| #ifdef WORDS_BIGENDIAN |
| bom = -bom; // switch things around for big endian |
| #endif |
| if (!bom) |
| { |
| #ifdef WORDS_BIGENDIAN |
| unicode += ch2; |
| unicode += ch1; |
| for (size_t i = 0; i < len; i += 2) |
| { |
| if (!readTwoChars(reader, ch1, ch2)) |
| { |
| break; |
| } |
| unicode += ch2; |
| unicode += ch1; |
| } |
| #else |
| unicode += ch1; |
| unicode += ch2; |
| unicode += readText(reader, len); |
| #endif |
| } |
| else if (bom == -1) |
| { |
| unicode = readText(reader, len); |
| } |
| else |
| { |
| for (size_t i = 0; i < len; i += 2) |
| { |
| if (!readTwoChars(reader, ch1, ch2)) |
| { |
| break; |
| } |
| unicode += ch2; |
| unicode += ch1; |
| } |
| } |
| unicode += '\0'; // ESL: need to terminate double-byte string properly. |
| return unicode; |
| } |
| |
| BString io::readAllBinary(ID3_Reader& reader) |
| { |
| return readBinary(reader, reader.remainingBytes()); |
| } |
| |
| BString io::readBinary(ID3_Reader& reader, size_t len) |
| { |
| BString binary; |
| binary.reserve(len); |
| |
| size_t remaining = len; |
| const size_t SIZE = 1024; |
| ID3_Reader::char_type buf[SIZE]; |
| while (!reader.atEnd() && remaining > 0) |
| { |
| size_t numRead = reader.readChars(buf, min(remaining, SIZE)); |
| remaining -= numRead; |
| binary.append(reinterpret_cast<BString::value_type *>(buf), numRead); |
| } |
| |
| return binary; |
| } |
| |
| uint32 io::readLENumber(ID3_Reader& reader, size_t len) |
| { |
| uint32 val = 0; |
| for (size_t i = 0; i < len; i++) |
| { |
| if (reader.atEnd()) |
| { |
| break; |
| } |
| val += (static_cast<uint32>(0xFF & reader.readChar()) << (i * 8)); |
| } |
| return val; |
| } |
| |
| uint32 io::readBENumber(ID3_Reader& reader, size_t len) |
| { |
| uint32 val = 0; |
| |
| for (ID3_Reader::size_type i = 0; i < len && !reader.atEnd(); ++i) |
| { |
| val *= 256; // 2^8 |
| val += static_cast<uint32>(0xFF & reader.readChar()); |
| } |
| return val; |
| } |
| |
| String io::readTrailingSpaces(ID3_Reader& reader, size_t len) |
| { |
| io::WindowedReader wr(reader, len); |
| String str; |
| String spaces; |
| str.reserve(len); |
| spaces.reserve(len); |
| while (!wr.atEnd()) |
| { |
| ID3_Reader::char_type ch = wr.readChar(); |
| if (ch == '\0' || ch == ' ') |
| { |
| spaces += ch; |
| } |
| else |
| { |
| str += spaces + (char) ch; |
| spaces.erase(); |
| } |
| } |
| return str; |
| } |
| |
| uint32 io::readUInt28(ID3_Reader& reader) |
| { |
| uint32 val = 0; |
| const unsigned short BITSUSED = 7; |
| const uint32 MAXVAL = MASK(BITSUSED * sizeof(uint32)); |
| // For each byte of the first 4 bytes in the string... |
| for (size_t i = 0; i < sizeof(uint32); ++i) |
| { |
| if (reader.atEnd()) |
| { |
| break; |
| } |
| // ...append the last 7 bits to the end of the temp integer... |
| val = (val << BITSUSED) | static_cast<uint32>(reader.readChar()) & MASK(BITSUSED); |
| } |
| |
| // We should always parse 4 characters |
| return min(val, MAXVAL); |
| } |
| |
| size_t io::writeBENumber(ID3_Writer& writer, uint32 val, size_t len) |
| { |
| ID3_Writer::char_type bytes[sizeof(uint32)]; |
| ID3_Writer::size_type size = min<ID3_Reader::size_type>(len, sizeof(uint32)); |
| renderNumber(bytes, val, size); |
| return writer.writeChars(bytes, size); |
| } |
| |
| size_t io::writeTrailingSpaces(ID3_Writer& writer, String buf, size_t len) |
| { |
| ID3_Writer::pos_type beg = writer.getCur(); |
| ID3_Writer::size_type strLen = buf.size(); |
| ID3_Writer::size_type size = min((unsigned int)len, (unsigned int)strLen); |
| writer.writeChars(buf.data(), size); |
| for (; size < len; ++size) |
| { |
| writer.writeChar('\0'); |
| } |
| return writer.getCur() - beg; |
| } |
| |
| size_t io::writeUInt28(ID3_Writer& writer, uint32 val) |
| { |
| uchar data[sizeof(uint32)]; |
| const unsigned short BITSUSED = 7; |
| const uint32 MAXVAL = MASK(BITSUSED * sizeof(uint32)); |
| val = min(val, MAXVAL); |
| // This loop renders the value to the character buffer in reverse order, as |
| // it is easy to extract the last 7 bits of an integer. This is why the |
| // loop shifts the value of the integer by 7 bits for each iteration. |
| for (size_t i = 0; i < sizeof(uint32); ++i) |
| { |
| // Extract the last BITSUSED bits from val and put it in its appropriate |
| // place in the data buffer |
| data[sizeof(uint32) - i - 1] = static_cast<uchar>(val & MASK(BITSUSED)); |
| |
| // The last BITSUSED bits were extracted from the val. So shift it to the |
| // right by that many bits for the next iteration |
| val >>= BITSUSED; |
| } |
| |
| // Should always render 4 bytes |
| return writer.writeChars(data, sizeof(uint32)); |
| } |
| |
| size_t io::writeString(ID3_Writer& writer, String data) |
| { |
| size_t size = writeText(writer, data); |
| writer.writeChar('\0'); |
| return size + 1; |
| } |
| |
| size_t io::writeText(ID3_Writer& writer, String data) |
| { |
| ID3_Writer::pos_type beg = writer.getCur(); |
| writer.writeChars(data.data(), data.size()); |
| return writer.getCur() - beg; |
| } |
| |
| size_t io::writeUnicodeString(ID3_Writer& writer, String data, bool bom) |
| { |
| size_t size = writeUnicodeText(writer, data, bom); |
| unicode_t null = NULL_UNICODE; |
| writer.writeChars((const unsigned char*) &null, 2); |
| return size + 2; |
| } |
| |
| size_t io::writeUnicodeText(ID3_Writer& writer, String data, bool bom) |
| { |
| ID3_Writer::pos_type beg = writer.getCur(); |
| size_t size = (data.size() / 2) * 2; |
| if (size == 0) |
| { |
| return 0; |
| } |
| int is_bom = isBOM(data[0],data[1]); |
| if (!is_bom && bom) { |
| // Write the BOM: 0xFEFF |
| const unsigned char BOMch1 = 0xFE; |
| const unsigned char BOMch2 = 0xFF; |
| writer.writeChars(&BOMch1, 1); |
| writer.writeChars(&BOMch2, 1); |
| } |
| for (size_t i = 0; i < size; i += 2) |
| { |
| if (!i && !bom && is_bom) { |
| // Skip unneeded leading BOM |
| continue; |
| } |
| if (is_bom >= 0) { |
| writer.writeChars(data.data()+i, 1); |
| writer.writeChars(data.data()+i+1, 1); |
| } |
| else { |
| writer.writeChars(data.data()+i+1, 1); |
| writer.writeChars(data.data()+i, 1); |
| } |
| } |
| return writer.getCur() - beg; |
| } |
| |