fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
164
modules/juce_core/text/juce_Base64.cpp
Normal file
164
modules/juce_core/text/juce_Base64.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
bool Base64::convertToBase64 (OutputStream& base64Result, const void* sourceData, size_t sourceDataSize)
|
||||
{
|
||||
static const char lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
auto* source = static_cast<const uint8*> (sourceData);
|
||||
|
||||
while (sourceDataSize > 0)
|
||||
{
|
||||
char frame[4];
|
||||
auto byte0 = *source++;
|
||||
frame[0] = lookup [(byte0 & 0xfcu) >> 2];
|
||||
uint32 bits = (byte0 & 0x03u) << 4;
|
||||
|
||||
if (sourceDataSize > 1)
|
||||
{
|
||||
auto byte1 = *source++;
|
||||
frame[1] = lookup[bits | ((byte1 & 0xf0u) >> 4)];
|
||||
bits = (byte1 & 0x0fu) << 2;
|
||||
|
||||
if (sourceDataSize > 2)
|
||||
{
|
||||
auto byte2 = *source++;
|
||||
frame[2] = lookup[bits | ((byte2 & 0xc0u) >> 6)];
|
||||
frame[3] = lookup[byte2 & 0x3fu];
|
||||
sourceDataSize -= 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame[2] = lookup[bits];
|
||||
frame[3] = '=';
|
||||
sourceDataSize = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame[1] = lookup[bits];
|
||||
frame[2] = '=';
|
||||
frame[3] = '=';
|
||||
sourceDataSize = 0;
|
||||
}
|
||||
|
||||
if (! base64Result.write (frame, 4))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Base64::convertFromBase64 (OutputStream& binaryOutput, StringRef base64TextInput)
|
||||
{
|
||||
for (auto s = base64TextInput.text; ! s.isEmpty();)
|
||||
{
|
||||
uint8 data[4];
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
auto c = (uint32) s.getAndAdvance();
|
||||
|
||||
if (c >= 'A' && c <= 'Z') c -= 'A';
|
||||
else if (c >= 'a' && c <= 'z') c -= 'a' - 26;
|
||||
else if (c >= '0' && c <= '9') c += 52 - '0';
|
||||
else if (c == '+') c = 62;
|
||||
else if (c == '/') c = 63;
|
||||
else if (c == '=') { c = 64; if (i <= 1) return false; }
|
||||
else return false;
|
||||
|
||||
data[i] = (uint8) c;
|
||||
}
|
||||
|
||||
binaryOutput.writeByte ((char) ((data[0] << 2) | (data[1] >> 4)));
|
||||
|
||||
if (data[2] < 64)
|
||||
{
|
||||
binaryOutput.writeByte ((char) ((data[1] << 4) | (data[2] >> 2)));
|
||||
|
||||
if (data[3] < 64)
|
||||
binaryOutput.writeByte ((char) ((data[2] << 6) | data[3]));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String Base64::toBase64 (const void* sourceData, size_t sourceDataSize)
|
||||
{
|
||||
MemoryOutputStream m ((sourceDataSize * 4) / 3 + 3);
|
||||
bool ok = convertToBase64 (m, sourceData, sourceDataSize);
|
||||
jassert (ok); // should always succeed for this simple case
|
||||
ignoreUnused (ok);
|
||||
return m.toString();
|
||||
}
|
||||
|
||||
String Base64::toBase64 (const String& text)
|
||||
{
|
||||
return toBase64 (text.toRawUTF8(), strlen (text.toRawUTF8()));
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class Base64Tests : public UnitTest
|
||||
{
|
||||
public:
|
||||
Base64Tests() : UnitTest ("Base64 class", "Text") {}
|
||||
|
||||
static MemoryBlock createRandomData (Random& r)
|
||||
{
|
||||
MemoryOutputStream m;
|
||||
|
||||
for (int i = r.nextInt (400); --i >= 0;)
|
||||
m.writeByte ((char) r.nextInt (256));
|
||||
|
||||
return m.getMemoryBlock();
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("Base64");
|
||||
|
||||
auto r = getRandom();
|
||||
|
||||
for (int i = 1000; --i >= 0;)
|
||||
{
|
||||
auto original = createRandomData (r);
|
||||
auto asBase64 = Base64::toBase64 (original.getData(), original.getSize());
|
||||
MemoryOutputStream out;
|
||||
expect (Base64::convertFromBase64 (out, asBase64));
|
||||
auto result = out.getMemoryBlock();
|
||||
expect (result == original);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static Base64Tests base64Tests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
53
modules/juce_core/text/juce_Base64.h
Normal file
53
modules/juce_core/text/juce_Base64.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Contains some static methods for converting between binary and the
|
||||
standard base-64 encoding format.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
struct JUCE_API Base64
|
||||
{
|
||||
/** Converts a binary block of data into a base-64 string.
|
||||
This will write the resulting string data to the given stream.
|
||||
If a write error occurs with the stream, the method will terminate and return false.
|
||||
*/
|
||||
static bool convertToBase64 (OutputStream& base64Result, const void* sourceData, size_t sourceDataSize);
|
||||
|
||||
/** Converts a base-64 string back to its binary representation.
|
||||
This will write the decoded binary data to the given stream.
|
||||
If the string is not valid base-64, the method will terminate and return false.
|
||||
*/
|
||||
static bool convertFromBase64 (OutputStream& binaryOutput, StringRef base64TextInput);
|
||||
|
||||
/** Converts a block of binary data to a base-64 string. */
|
||||
static String toBase64 (const void* sourceData, size_t sourceDataSize);
|
||||
|
||||
/** Converts a string's UTF-8 representation to a base-64 string. */
|
||||
static String toBase64 (const String& textToEncode);
|
||||
};
|
||||
|
||||
} // namespace juce
|
380
modules/juce_core/text/juce_CharPointer_ASCII.h
Normal file
380
modules/juce_core/text/juce_CharPointer_ASCII.h
Normal file
@ -0,0 +1,380 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Wraps a pointer to a null-terminated ASCII character string, and provides
|
||||
various methods to operate on the data.
|
||||
|
||||
A valid ASCII string is assumed to not contain any characters above 127.
|
||||
|
||||
@see CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class CharPointer_ASCII final
|
||||
{
|
||||
public:
|
||||
using CharType = char;
|
||||
|
||||
inline explicit CharPointer_ASCII (const CharType* rawPointer) noexcept
|
||||
: data (const_cast<CharType*> (rawPointer))
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_ASCII (const CharPointer_ASCII& other) noexcept
|
||||
: data (other.data)
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_ASCII operator= (const CharPointer_ASCII other) noexcept
|
||||
{
|
||||
data = other.data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CharPointer_ASCII operator= (const CharType* text) noexcept
|
||||
{
|
||||
data = const_cast<CharType*> (text);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** This is a pointer comparison, it doesn't compare the actual text. */
|
||||
inline bool operator== (CharPointer_ASCII other) const noexcept { return data == other.data; }
|
||||
inline bool operator!= (CharPointer_ASCII other) const noexcept { return data != other.data; }
|
||||
inline bool operator<= (CharPointer_ASCII other) const noexcept { return data <= other.data; }
|
||||
inline bool operator< (CharPointer_ASCII other) const noexcept { return data < other.data; }
|
||||
inline bool operator>= (CharPointer_ASCII other) const noexcept { return data >= other.data; }
|
||||
inline bool operator> (CharPointer_ASCII other) const noexcept { return data > other.data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline CharType* getAddress() const noexcept { return data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline operator const CharType*() const noexcept { return data; }
|
||||
|
||||
/** Returns true if this pointer is pointing to a null character. */
|
||||
inline bool isEmpty() const noexcept { return *data == 0; }
|
||||
|
||||
/** Returns true if this pointer is not pointing to a null character. */
|
||||
inline bool isNotEmpty() const noexcept { return *data != 0; }
|
||||
|
||||
/** Returns the unicode character that this pointer is pointing to. */
|
||||
inline juce_wchar operator*() const noexcept { return (juce_wchar) (uint8) *data; }
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
inline CharPointer_ASCII operator++() noexcept
|
||||
{
|
||||
++data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Moves this pointer to the previous character in the string. */
|
||||
inline CharPointer_ASCII operator--() noexcept
|
||||
{
|
||||
--data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns the character that this pointer is currently pointing to, and then
|
||||
advances the pointer to point to the next character. */
|
||||
inline juce_wchar getAndAdvance() noexcept { return (juce_wchar) (uint8) *data++; }
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
CharPointer_ASCII operator++ (int) noexcept
|
||||
{
|
||||
auto temp (*this);
|
||||
++data;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/** Moves this pointer forwards by the specified number of characters. */
|
||||
inline void operator+= (const int numToSkip) noexcept
|
||||
{
|
||||
data += numToSkip;
|
||||
}
|
||||
|
||||
inline void operator-= (const int numToSkip) noexcept
|
||||
{
|
||||
data -= numToSkip;
|
||||
}
|
||||
|
||||
/** Returns the character at a given character index from the start of the string. */
|
||||
inline juce_wchar operator[] (const int characterIndex) const noexcept
|
||||
{
|
||||
return (juce_wchar) (uint8) data [characterIndex];
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved forwards from this one by the specified number of characters. */
|
||||
CharPointer_ASCII operator+ (const int numToSkip) const noexcept
|
||||
{
|
||||
return CharPointer_ASCII (data + numToSkip);
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved backwards from this one by the specified number of characters. */
|
||||
CharPointer_ASCII operator- (const int numToSkip) const noexcept
|
||||
{
|
||||
return CharPointer_ASCII (data - numToSkip);
|
||||
}
|
||||
|
||||
/** Writes a unicode character to this string, and advances this pointer to point to the next position. */
|
||||
inline void write (const juce_wchar charToWrite) noexcept
|
||||
{
|
||||
*data++ = (char) charToWrite;
|
||||
}
|
||||
|
||||
inline void replaceChar (const juce_wchar newChar) noexcept
|
||||
{
|
||||
*data = (char) newChar;
|
||||
}
|
||||
|
||||
/** Writes a null character to this string (leaving the pointer's position unchanged). */
|
||||
inline void writeNull() const noexcept
|
||||
{
|
||||
*data = 0;
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string. */
|
||||
size_t length() const noexcept
|
||||
{
|
||||
return (size_t) strlen (data);
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or the given value, whichever is lower. */
|
||||
size_t lengthUpTo (const size_t maxCharsToCount) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, maxCharsToCount);
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */
|
||||
size_t lengthUpTo (const CharPointer_ASCII end) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, end);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that are used to represent this string.
|
||||
This includes the terminating null character.
|
||||
*/
|
||||
size_t sizeInBytes() const noexcept
|
||||
{
|
||||
return length() + 1;
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
unicode character in this encoding format.
|
||||
*/
|
||||
static inline size_t getBytesRequiredFor (const juce_wchar) noexcept
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
string in this encoding format.
|
||||
The value returned does NOT include the terminating null character.
|
||||
*/
|
||||
template <class CharPointer>
|
||||
static size_t getBytesRequiredFor (const CharPointer text) noexcept
|
||||
{
|
||||
return text.length();
|
||||
}
|
||||
|
||||
/** Returns a pointer to the null character that terminates this string. */
|
||||
CharPointer_ASCII findTerminatingNull() const noexcept
|
||||
{
|
||||
return CharPointer_ASCII (data + length());
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes. */
|
||||
template <typename CharPointer>
|
||||
void writeAll (const CharPointer src) noexcept
|
||||
{
|
||||
CharacterFunctions::copyAll (*this, src);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxDestBytes parameter specifies the maximum number of bytes that can be written
|
||||
to the destination buffer before stopping.
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept
|
||||
{
|
||||
return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxChars parameter specifies the maximum number of characters that can be
|
||||
written to the destination buffer before stopping (including the terminating null).
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept
|
||||
{
|
||||
CharacterFunctions::copyWithCharLimit (*this, src, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compare (const CharPointer other) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compare (*this, other);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
int compare (const CharPointer_ASCII other) const noexcept
|
||||
{
|
||||
return strcmp (data, other.data);
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareUpTo (const CharPointer other, const int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
int compareUpTo (const CharPointer_ASCII other, const int maxChars) const noexcept
|
||||
{
|
||||
return strncmp (data, other.data, (size_t) maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCase (const CharPointer other) const
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCase (*this, other);
|
||||
}
|
||||
|
||||
int compareIgnoreCase (const CharPointer_ASCII other) const
|
||||
{
|
||||
#if JUCE_MINGW || (JUCE_WINDOWS && JUCE_CLANG)
|
||||
return CharacterFunctions::compareIgnoreCase (*this, other);
|
||||
#elif JUCE_WINDOWS
|
||||
return stricmp (data, other.data);
|
||||
#else
|
||||
return strcasecmp (data, other.data);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
/** Returns the character index of a substring, or -1 if it isn't found. */
|
||||
template <typename CharPointer>
|
||||
int indexOf (const CharPointer stringToFind) const noexcept
|
||||
{
|
||||
return CharacterFunctions::indexOf (*this, stringToFind);
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (const juce_wchar charToFind) const noexcept
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (data[i] != 0)
|
||||
{
|
||||
if (data[i] == (char) charToFind)
|
||||
return i;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (const juce_wchar charToFind, const bool ignoreCase) const noexcept
|
||||
{
|
||||
return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind)
|
||||
: CharacterFunctions::indexOfChar (*this, charToFind);
|
||||
}
|
||||
|
||||
/** Returns true if the first character of this string is whitespace. */
|
||||
bool isWhitespace() const { return CharacterFunctions::isWhitespace (*data) != 0; }
|
||||
/** Returns true if the first character of this string is a digit. */
|
||||
bool isDigit() const { return CharacterFunctions::isDigit (*data) != 0; }
|
||||
/** Returns true if the first character of this string is a letter. */
|
||||
bool isLetter() const { return CharacterFunctions::isLetter (*data) != 0; }
|
||||
/** Returns true if the first character of this string is a letter or digit. */
|
||||
bool isLetterOrDigit() const { return CharacterFunctions::isLetterOrDigit (*data) != 0; }
|
||||
/** Returns true if the first character of this string is upper-case. */
|
||||
bool isUpperCase() const { return CharacterFunctions::isUpperCase ((juce_wchar) (uint8) *data) != 0; }
|
||||
/** Returns true if the first character of this string is lower-case. */
|
||||
bool isLowerCase() const { return CharacterFunctions::isLowerCase ((juce_wchar) (uint8) *data) != 0; }
|
||||
|
||||
/** Returns an upper-case version of the first character of this string. */
|
||||
juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase ((juce_wchar) (uint8) *data); }
|
||||
/** Returns a lower-case version of the first character of this string. */
|
||||
juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase ((juce_wchar) (uint8) *data); }
|
||||
|
||||
/** Parses this string as a 32-bit integer. */
|
||||
int getIntValue32() const noexcept { return atoi (data); }
|
||||
|
||||
/** Parses this string as a 64-bit integer. */
|
||||
int64 getIntValue64() const noexcept
|
||||
{
|
||||
#if JUCE_LINUX || JUCE_ANDROID || JUCE_MINGW
|
||||
return atoll (data);
|
||||
#elif JUCE_WINDOWS
|
||||
return _atoi64 (data);
|
||||
#else
|
||||
return CharacterFunctions::getIntValue <int64, CharPointer_ASCII> (*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Parses this string as a floating point double. */
|
||||
double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); }
|
||||
|
||||
/** Returns the first non-whitespace character in the string. */
|
||||
CharPointer_ASCII findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); }
|
||||
|
||||
/** Returns true if the given unicode character can be represented in this encoding. */
|
||||
static bool canRepresent (juce_wchar character) noexcept
|
||||
{
|
||||
return ((unsigned int) character) < (unsigned int) 128;
|
||||
}
|
||||
|
||||
/** Returns true if this data contains a valid string in this encoding. */
|
||||
static bool isValidString (const CharType* dataToTest, int maxBytesToRead)
|
||||
{
|
||||
while (--maxBytesToRead >= 0)
|
||||
{
|
||||
if (((signed char) *dataToTest) <= 0)
|
||||
return *dataToTest == 0;
|
||||
|
||||
++dataToTest;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
CharType* data;
|
||||
};
|
||||
|
||||
} // namespace juce
|
522
modules/juce_core/text/juce_CharPointer_UTF16.h
Normal file
522
modules/juce_core/text/juce_CharPointer_UTF16.h
Normal file
@ -0,0 +1,522 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Wraps a pointer to a null-terminated UTF-16 character string, and provides
|
||||
various methods to operate on the data.
|
||||
@see CharPointer_UTF8, CharPointer_UTF32
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class CharPointer_UTF16 final
|
||||
{
|
||||
public:
|
||||
#if JUCE_NATIVE_WCHAR_IS_UTF16
|
||||
using CharType = wchar_t;
|
||||
#else
|
||||
using CharType = int16;
|
||||
#endif
|
||||
|
||||
inline explicit CharPointer_UTF16 (const CharType* rawPointer) noexcept
|
||||
: data (const_cast<CharType*> (rawPointer))
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_UTF16 (const CharPointer_UTF16& other) noexcept
|
||||
: data (other.data)
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_UTF16 operator= (CharPointer_UTF16 other) noexcept
|
||||
{
|
||||
data = other.data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CharPointer_UTF16 operator= (const CharType* text) noexcept
|
||||
{
|
||||
data = const_cast<CharType*> (text);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** This is a pointer comparison, it doesn't compare the actual text. */
|
||||
inline bool operator== (CharPointer_UTF16 other) const noexcept { return data == other.data; }
|
||||
inline bool operator!= (CharPointer_UTF16 other) const noexcept { return data != other.data; }
|
||||
inline bool operator<= (CharPointer_UTF16 other) const noexcept { return data <= other.data; }
|
||||
inline bool operator< (CharPointer_UTF16 other) const noexcept { return data < other.data; }
|
||||
inline bool operator>= (CharPointer_UTF16 other) const noexcept { return data >= other.data; }
|
||||
inline bool operator> (CharPointer_UTF16 other) const noexcept { return data > other.data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline CharType* getAddress() const noexcept { return data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline operator const CharType*() const noexcept { return data; }
|
||||
|
||||
/** Returns true if this pointer is pointing to a null character. */
|
||||
inline bool isEmpty() const noexcept { return *data == 0; }
|
||||
|
||||
/** Returns true if this pointer is not pointing to a null character. */
|
||||
inline bool isNotEmpty() const noexcept { return *data != 0; }
|
||||
|
||||
/** Returns the unicode character that this pointer is pointing to. */
|
||||
juce_wchar operator*() const noexcept
|
||||
{
|
||||
auto n = (uint32) (uint16) *data;
|
||||
|
||||
if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) data[1]) >= 0xdc00)
|
||||
n = 0x10000 + (((n - 0xd800) << 10) | (((uint32) (uint16) data[1]) - 0xdc00));
|
||||
|
||||
return (juce_wchar) n;
|
||||
}
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
CharPointer_UTF16 operator++() noexcept
|
||||
{
|
||||
auto n = (uint32) (uint16) *data++;
|
||||
|
||||
if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) *data) >= 0xdc00)
|
||||
++data;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Moves this pointer back to the previous character in the string. */
|
||||
CharPointer_UTF16 operator--() noexcept
|
||||
{
|
||||
auto n = (uint32) (uint16) (*--data);
|
||||
|
||||
if (n >= 0xdc00 && n <= 0xdfff)
|
||||
--data;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns the character that this pointer is currently pointing to, and then
|
||||
advances the pointer to point to the next character. */
|
||||
juce_wchar getAndAdvance() noexcept
|
||||
{
|
||||
auto n = (uint32) (uint16) *data++;
|
||||
|
||||
if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) *data) >= 0xdc00)
|
||||
n = 0x10000 + ((((n - 0xd800) << 10) | (((uint32) (uint16) *data++) - 0xdc00)));
|
||||
|
||||
return (juce_wchar) n;
|
||||
}
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
CharPointer_UTF16 operator++ (int) noexcept
|
||||
{
|
||||
auto temp (*this);
|
||||
++*this;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/** Moves this pointer forwards by the specified number of characters. */
|
||||
void operator+= (int numToSkip) noexcept
|
||||
{
|
||||
if (numToSkip < 0)
|
||||
{
|
||||
while (++numToSkip <= 0)
|
||||
--*this;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (--numToSkip >= 0)
|
||||
++*this;
|
||||
}
|
||||
}
|
||||
|
||||
/** Moves this pointer backwards by the specified number of characters. */
|
||||
void operator-= (int numToSkip) noexcept
|
||||
{
|
||||
operator+= (-numToSkip);
|
||||
}
|
||||
|
||||
/** Returns the character at a given character index from the start of the string. */
|
||||
juce_wchar operator[] (int characterIndex) const noexcept
|
||||
{
|
||||
auto p (*this);
|
||||
p += characterIndex;
|
||||
return *p;
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved forwards from this one by the specified number of characters. */
|
||||
CharPointer_UTF16 operator+ (int numToSkip) const noexcept
|
||||
{
|
||||
auto p (*this);
|
||||
p += numToSkip;
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved backwards from this one by the specified number of characters. */
|
||||
CharPointer_UTF16 operator- (int numToSkip) const noexcept
|
||||
{
|
||||
auto p (*this);
|
||||
p += -numToSkip;
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Writes a unicode character to this string, and advances this pointer to point to the next position. */
|
||||
void write (juce_wchar charToWrite) noexcept
|
||||
{
|
||||
if (charToWrite >= 0x10000)
|
||||
{
|
||||
charToWrite -= 0x10000;
|
||||
*data++ = (CharType) (0xd800 + (charToWrite >> 10));
|
||||
*data++ = (CharType) (0xdc00 + (charToWrite & 0x3ff));
|
||||
}
|
||||
else
|
||||
{
|
||||
*data++ = (CharType) charToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes a null character to this string (leaving the pointer's position unchanged). */
|
||||
inline void writeNull() const noexcept
|
||||
{
|
||||
*data = 0;
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string. */
|
||||
size_t length() const noexcept
|
||||
{
|
||||
auto* d = data;
|
||||
size_t count = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto n = (uint32) (uint16) *d++;
|
||||
|
||||
if (n >= 0xd800 && n <= 0xdfff)
|
||||
{
|
||||
if (*d++ == 0)
|
||||
break;
|
||||
}
|
||||
else if (n == 0)
|
||||
break;
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or the given value, whichever is lower. */
|
||||
size_t lengthUpTo (size_t maxCharsToCount) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, maxCharsToCount);
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */
|
||||
size_t lengthUpTo (CharPointer_UTF16 end) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, end);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that are used to represent this string.
|
||||
This includes the terminating null character.
|
||||
*/
|
||||
size_t sizeInBytes() const noexcept
|
||||
{
|
||||
return sizeof (CharType) * (findNullIndex (data) + 1);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
unicode character in this encoding format.
|
||||
*/
|
||||
static size_t getBytesRequiredFor (juce_wchar charToWrite) noexcept
|
||||
{
|
||||
return (charToWrite >= 0x10000) ? (sizeof (CharType) * 2) : sizeof (CharType);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
string in this encoding format.
|
||||
The value returned does NOT include the terminating null character.
|
||||
*/
|
||||
template <class CharPointer>
|
||||
static size_t getBytesRequiredFor (CharPointer text) noexcept
|
||||
{
|
||||
size_t count = 0;
|
||||
juce_wchar n;
|
||||
|
||||
while ((n = text.getAndAdvance()) != 0)
|
||||
count += getBytesRequiredFor (n);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Returns a pointer to the null character that terminates this string. */
|
||||
CharPointer_UTF16 findTerminatingNull() const noexcept
|
||||
{
|
||||
auto* t = data;
|
||||
|
||||
while (*t != 0)
|
||||
++t;
|
||||
|
||||
return CharPointer_UTF16 (t);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes. */
|
||||
template <typename CharPointer>
|
||||
void writeAll (CharPointer src) noexcept
|
||||
{
|
||||
CharacterFunctions::copyAll (*this, src);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes. */
|
||||
void writeAll (CharPointer_UTF16 src) noexcept
|
||||
{
|
||||
auto* s = src.data;
|
||||
|
||||
while ((*data = *s) != 0)
|
||||
{
|
||||
++data;
|
||||
++s;
|
||||
}
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxDestBytes parameter specifies the maximum number of bytes that can be written
|
||||
to the destination buffer before stopping.
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
size_t writeWithDestByteLimit (CharPointer src, size_t maxDestBytes) noexcept
|
||||
{
|
||||
return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxChars parameter specifies the maximum number of characters that can be
|
||||
written to the destination buffer before stopping (including the terminating null).
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
void writeWithCharLimit (CharPointer src, int maxChars) noexcept
|
||||
{
|
||||
CharacterFunctions::copyWithCharLimit (*this, src, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compare (CharPointer other) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compare (*this, other);
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareUpTo (CharPointer other, int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCase (CharPointer other) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCase (*this, other);
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCaseUpTo (CharPointer other, int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
#if JUCE_MSVC && ! DOXYGEN
|
||||
int compareIgnoreCase (CharPointer_UTF16 other) const noexcept
|
||||
{
|
||||
return _wcsicmp (data, other.data);
|
||||
}
|
||||
|
||||
int compareIgnoreCaseUpTo (CharPointer_UTF16 other, int maxChars) const noexcept
|
||||
{
|
||||
return _wcsnicmp (data, other.data, (size_t) maxChars);
|
||||
}
|
||||
|
||||
int indexOf (CharPointer_UTF16 stringToFind) const noexcept
|
||||
{
|
||||
const CharType* const t = wcsstr (data, stringToFind.getAddress());
|
||||
return t == nullptr ? -1 : (int) (t - data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Returns the character index of a substring, or -1 if it isn't found. */
|
||||
template <typename CharPointer>
|
||||
int indexOf (CharPointer stringToFind) const noexcept
|
||||
{
|
||||
return CharacterFunctions::indexOf (*this, stringToFind);
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (juce_wchar charToFind) const noexcept
|
||||
{
|
||||
return CharacterFunctions::indexOfChar (*this, charToFind);
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (juce_wchar charToFind, bool ignoreCase) const noexcept
|
||||
{
|
||||
return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind)
|
||||
: CharacterFunctions::indexOfChar (*this, charToFind);
|
||||
}
|
||||
|
||||
/** Returns true if the first character of this string is whitespace. */
|
||||
bool isWhitespace() const noexcept { return CharacterFunctions::isWhitespace (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is a digit. */
|
||||
bool isDigit() const noexcept { return CharacterFunctions::isDigit (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is a letter. */
|
||||
bool isLetter() const noexcept { return CharacterFunctions::isLetter (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is a letter or digit. */
|
||||
bool isLetterOrDigit() const noexcept { return CharacterFunctions::isLetterOrDigit (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is upper-case. */
|
||||
bool isUpperCase() const noexcept { return CharacterFunctions::isUpperCase (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is lower-case. */
|
||||
bool isLowerCase() const noexcept { return CharacterFunctions::isLowerCase (operator*()) != 0; }
|
||||
|
||||
/** Returns an upper-case version of the first character of this string. */
|
||||
juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); }
|
||||
/** Returns a lower-case version of the first character of this string. */
|
||||
juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); }
|
||||
|
||||
/** Parses this string as a 32-bit integer. */
|
||||
int getIntValue32() const noexcept
|
||||
{
|
||||
#if JUCE_MSVC
|
||||
return _wtoi (data);
|
||||
#else
|
||||
return CharacterFunctions::getIntValue<int, CharPointer_UTF16> (*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Parses this string as a 64-bit integer. */
|
||||
int64 getIntValue64() const noexcept
|
||||
{
|
||||
#if JUCE_MSVC
|
||||
return _wtoi64 (data);
|
||||
#else
|
||||
return CharacterFunctions::getIntValue<int64, CharPointer_UTF16> (*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Parses this string as a floating point double. */
|
||||
double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); }
|
||||
|
||||
/** Returns the first non-whitespace character in the string. */
|
||||
CharPointer_UTF16 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); }
|
||||
|
||||
/** Returns true if the given unicode character can be represented in this encoding. */
|
||||
static bool canRepresent (juce_wchar character) noexcept
|
||||
{
|
||||
auto n = (uint32) character;
|
||||
return n < 0x10ffff && (n < 0xd800 || n > 0xdfff);
|
||||
}
|
||||
|
||||
/** Returns true if this data contains a valid string in this encoding. */
|
||||
static bool isValidString (const CharType* dataToTest, int maxBytesToRead)
|
||||
{
|
||||
maxBytesToRead /= (int) sizeof (CharType);
|
||||
|
||||
while (--maxBytesToRead >= 0 && *dataToTest != 0)
|
||||
{
|
||||
auto n = (uint32) (uint16) *dataToTest++;
|
||||
|
||||
if (n >= 0xd800)
|
||||
{
|
||||
if (n > 0x10ffff)
|
||||
return false;
|
||||
|
||||
if (n <= 0xdfff)
|
||||
{
|
||||
if (n > 0xdc00)
|
||||
return false;
|
||||
|
||||
auto nextChar = (uint32) (uint16) *dataToTest++;
|
||||
|
||||
if (nextChar < 0xdc00 || nextChar > 0xdfff)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Atomically swaps this pointer for a new value, returning the previous value. */
|
||||
CharPointer_UTF16 atomicSwap (CharPointer_UTF16 newValue)
|
||||
{
|
||||
return CharPointer_UTF16 (reinterpret_cast<Atomic<CharType*>&> (data).exchange (newValue.data));
|
||||
}
|
||||
|
||||
/** These values are the byte-order-mark (BOM) values for a UTF-16 stream. */
|
||||
enum
|
||||
{
|
||||
byteOrderMarkBE1 = 0xfe,
|
||||
byteOrderMarkBE2 = 0xff,
|
||||
byteOrderMarkLE1 = 0xff,
|
||||
byteOrderMarkLE2 = 0xfe
|
||||
};
|
||||
|
||||
/** Returns true if the first pair of bytes in this pointer are the UTF16 byte-order mark (big endian).
|
||||
The pointer must not be null, and must contain at least two valid bytes.
|
||||
*/
|
||||
static bool isByteOrderMarkBigEndian (const void* possibleByteOrder) noexcept
|
||||
{
|
||||
jassert (possibleByteOrder != nullptr);
|
||||
auto c = static_cast<const uint8*> (possibleByteOrder);
|
||||
|
||||
return c[0] == (uint8) byteOrderMarkBE1
|
||||
&& c[1] == (uint8) byteOrderMarkBE2;
|
||||
}
|
||||
|
||||
/** Returns true if the first pair of bytes in this pointer are the UTF16 byte-order mark (little endian).
|
||||
The pointer must not be null, and must contain at least two valid bytes.
|
||||
*/
|
||||
static bool isByteOrderMarkLittleEndian (const void* possibleByteOrder) noexcept
|
||||
{
|
||||
jassert (possibleByteOrder != nullptr);
|
||||
auto c = static_cast<const uint8*> (possibleByteOrder);
|
||||
|
||||
return c[0] == (uint8) byteOrderMarkLE1
|
||||
&& c[1] == (uint8) byteOrderMarkLE2;
|
||||
}
|
||||
|
||||
private:
|
||||
CharType* data;
|
||||
|
||||
static unsigned int findNullIndex (const CharType* t) noexcept
|
||||
{
|
||||
unsigned int n = 0;
|
||||
|
||||
while (t[n] != 0)
|
||||
++n;
|
||||
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
375
modules/juce_core/text/juce_CharPointer_UTF32.h
Normal file
375
modules/juce_core/text/juce_CharPointer_UTF32.h
Normal file
@ -0,0 +1,375 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Wraps a pointer to a null-terminated UTF-32 character string, and provides
|
||||
various methods to operate on the data.
|
||||
@see CharPointer_UTF8, CharPointer_UTF16
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class CharPointer_UTF32 final
|
||||
{
|
||||
public:
|
||||
using CharType = juce_wchar;
|
||||
|
||||
inline explicit CharPointer_UTF32 (const CharType* rawPointer) noexcept
|
||||
: data (const_cast<CharType*> (rawPointer))
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_UTF32 (const CharPointer_UTF32& other) noexcept
|
||||
: data (other.data)
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_UTF32 operator= (CharPointer_UTF32 other) noexcept
|
||||
{
|
||||
data = other.data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CharPointer_UTF32 operator= (const CharType* text) noexcept
|
||||
{
|
||||
data = const_cast<CharType*> (text);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** This is a pointer comparison, it doesn't compare the actual text. */
|
||||
inline bool operator== (CharPointer_UTF32 other) const noexcept { return data == other.data; }
|
||||
inline bool operator!= (CharPointer_UTF32 other) const noexcept { return data != other.data; }
|
||||
inline bool operator<= (CharPointer_UTF32 other) const noexcept { return data <= other.data; }
|
||||
inline bool operator< (CharPointer_UTF32 other) const noexcept { return data < other.data; }
|
||||
inline bool operator>= (CharPointer_UTF32 other) const noexcept { return data >= other.data; }
|
||||
inline bool operator> (CharPointer_UTF32 other) const noexcept { return data > other.data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline CharType* getAddress() const noexcept { return data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline operator const CharType*() const noexcept { return data; }
|
||||
|
||||
/** Returns true if this pointer is pointing to a null character. */
|
||||
inline bool isEmpty() const noexcept { return *data == 0; }
|
||||
|
||||
/** Returns true if this pointer is not pointing to a null character. */
|
||||
inline bool isNotEmpty() const noexcept { return *data != 0; }
|
||||
|
||||
/** Returns the unicode character that this pointer is pointing to. */
|
||||
inline juce_wchar operator*() const noexcept { return *data; }
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
inline CharPointer_UTF32 operator++() noexcept
|
||||
{
|
||||
++data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Moves this pointer to the previous character in the string. */
|
||||
inline CharPointer_UTF32 operator--() noexcept
|
||||
{
|
||||
--data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns the character that this pointer is currently pointing to, and then
|
||||
advances the pointer to point to the next character. */
|
||||
inline juce_wchar getAndAdvance() noexcept { return *data++; }
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
CharPointer_UTF32 operator++ (int) noexcept
|
||||
{
|
||||
auto temp (*this);
|
||||
++data;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/** Moves this pointer forwards by the specified number of characters. */
|
||||
inline void operator+= (int numToSkip) noexcept
|
||||
{
|
||||
data += numToSkip;
|
||||
}
|
||||
|
||||
inline void operator-= (int numToSkip) noexcept
|
||||
{
|
||||
data -= numToSkip;
|
||||
}
|
||||
|
||||
/** Returns the character at a given character index from the start of the string. */
|
||||
inline juce_wchar& operator[] (int characterIndex) const noexcept
|
||||
{
|
||||
return data [characterIndex];
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved forwards from this one by the specified number of characters. */
|
||||
CharPointer_UTF32 operator+ (int numToSkip) const noexcept
|
||||
{
|
||||
return CharPointer_UTF32 (data + numToSkip);
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved backwards from this one by the specified number of characters. */
|
||||
CharPointer_UTF32 operator- (int numToSkip) const noexcept
|
||||
{
|
||||
return CharPointer_UTF32 (data - numToSkip);
|
||||
}
|
||||
|
||||
/** Writes a unicode character to this string, and advances this pointer to point to the next position. */
|
||||
inline void write (juce_wchar charToWrite) noexcept
|
||||
{
|
||||
*data++ = charToWrite;
|
||||
}
|
||||
|
||||
inline void replaceChar (juce_wchar newChar) noexcept
|
||||
{
|
||||
*data = newChar;
|
||||
}
|
||||
|
||||
/** Writes a null character to this string (leaving the pointer's position unchanged). */
|
||||
inline void writeNull() const noexcept
|
||||
{
|
||||
*data = 0;
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string. */
|
||||
size_t length() const noexcept
|
||||
{
|
||||
#if JUCE_NATIVE_WCHAR_IS_UTF32 && ! JUCE_ANDROID
|
||||
return wcslen (data);
|
||||
#else
|
||||
size_t n = 0;
|
||||
while (data[n] != 0)
|
||||
++n;
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or the given value, whichever is lower. */
|
||||
size_t lengthUpTo (size_t maxCharsToCount) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, maxCharsToCount);
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */
|
||||
size_t lengthUpTo (CharPointer_UTF32 end) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, end);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that are used to represent this string.
|
||||
This includes the terminating null character.
|
||||
*/
|
||||
size_t sizeInBytes() const noexcept
|
||||
{
|
||||
return sizeof (CharType) * (length() + 1);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
unicode character in this encoding format.
|
||||
*/
|
||||
static inline size_t getBytesRequiredFor (juce_wchar) noexcept
|
||||
{
|
||||
return sizeof (CharType);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
string in this encoding format.
|
||||
The value returned does NOT include the terminating null character.
|
||||
*/
|
||||
template <class CharPointer>
|
||||
static size_t getBytesRequiredFor (CharPointer text) noexcept
|
||||
{
|
||||
return sizeof (CharType) * text.length();
|
||||
}
|
||||
|
||||
/** Returns a pointer to the null character that terminates this string. */
|
||||
CharPointer_UTF32 findTerminatingNull() const noexcept
|
||||
{
|
||||
return CharPointer_UTF32 (data + length());
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes. */
|
||||
template <typename CharPointer>
|
||||
void writeAll (CharPointer src) noexcept
|
||||
{
|
||||
CharacterFunctions::copyAll (*this, src);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes. */
|
||||
void writeAll (CharPointer_UTF32 src) noexcept
|
||||
{
|
||||
auto* s = src.data;
|
||||
|
||||
while ((*data = *s) != 0)
|
||||
{
|
||||
++data;
|
||||
++s;
|
||||
}
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxDestBytes parameter specifies the maximum number of bytes that can be written
|
||||
to the destination buffer before stopping.
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
size_t writeWithDestByteLimit (CharPointer src, size_t maxDestBytes) noexcept
|
||||
{
|
||||
return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxChars parameter specifies the maximum number of characters that can be
|
||||
written to the destination buffer before stopping (including the terminating null).
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
void writeWithCharLimit (CharPointer src, int maxChars) noexcept
|
||||
{
|
||||
CharacterFunctions::copyWithCharLimit (*this, src, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compare (CharPointer other) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compare (*this, other);
|
||||
}
|
||||
|
||||
#if JUCE_NATIVE_WCHAR_IS_UTF32 && ! JUCE_ANDROID
|
||||
/** Compares this string with another one. */
|
||||
int compare (CharPointer_UTF32 other) const noexcept
|
||||
{
|
||||
return wcscmp (data, other.data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareUpTo (CharPointer other, int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCase (CharPointer other) const
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCase (*this, other);
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCaseUpTo (CharPointer other, int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
/** Returns the character index of a substring, or -1 if it isn't found. */
|
||||
template <typename CharPointer>
|
||||
int indexOf (CharPointer stringToFind) const noexcept
|
||||
{
|
||||
return CharacterFunctions::indexOf (*this, stringToFind);
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (juce_wchar charToFind) const noexcept
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (data[i] != 0)
|
||||
{
|
||||
if (data[i] == charToFind)
|
||||
return i;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (juce_wchar charToFind, bool ignoreCase) const noexcept
|
||||
{
|
||||
return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind)
|
||||
: CharacterFunctions::indexOfChar (*this, charToFind);
|
||||
}
|
||||
|
||||
/** Returns true if the first character of this string is whitespace. */
|
||||
bool isWhitespace() const { return CharacterFunctions::isWhitespace (*data) != 0; }
|
||||
/** Returns true if the first character of this string is a digit. */
|
||||
bool isDigit() const { return CharacterFunctions::isDigit (*data) != 0; }
|
||||
/** Returns true if the first character of this string is a letter. */
|
||||
bool isLetter() const { return CharacterFunctions::isLetter (*data) != 0; }
|
||||
/** Returns true if the first character of this string is a letter or digit. */
|
||||
bool isLetterOrDigit() const { return CharacterFunctions::isLetterOrDigit (*data) != 0; }
|
||||
/** Returns true if the first character of this string is upper-case. */
|
||||
bool isUpperCase() const { return CharacterFunctions::isUpperCase (*data) != 0; }
|
||||
/** Returns true if the first character of this string is lower-case. */
|
||||
bool isLowerCase() const { return CharacterFunctions::isLowerCase (*data) != 0; }
|
||||
|
||||
/** Returns an upper-case version of the first character of this string. */
|
||||
juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (*data); }
|
||||
/** Returns a lower-case version of the first character of this string. */
|
||||
juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (*data); }
|
||||
|
||||
/** Parses this string as a 32-bit integer. */
|
||||
int getIntValue32() const noexcept { return CharacterFunctions::getIntValue <int, CharPointer_UTF32> (*this); }
|
||||
/** Parses this string as a 64-bit integer. */
|
||||
int64 getIntValue64() const noexcept { return CharacterFunctions::getIntValue <int64, CharPointer_UTF32> (*this); }
|
||||
|
||||
/** Parses this string as a floating point double. */
|
||||
double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); }
|
||||
|
||||
/** Returns the first non-whitespace character in the string. */
|
||||
CharPointer_UTF32 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); }
|
||||
|
||||
/** Returns true if the given unicode character can be represented in this encoding. */
|
||||
static bool canRepresent (juce_wchar character) noexcept
|
||||
{
|
||||
return ((uint32) character) < (uint32) 0x10ffff;
|
||||
}
|
||||
|
||||
/** Returns true if this data contains a valid string in this encoding. */
|
||||
static bool isValidString (const CharType* dataToTest, int maxBytesToRead)
|
||||
{
|
||||
maxBytesToRead /= (int) sizeof (CharType);
|
||||
|
||||
while (--maxBytesToRead >= 0 && *dataToTest != 0)
|
||||
if (! canRepresent (*dataToTest++))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Atomically swaps this pointer for a new value, returning the previous value. */
|
||||
CharPointer_UTF32 atomicSwap (CharPointer_UTF32 newValue)
|
||||
{
|
||||
return CharPointer_UTF32 (reinterpret_cast<Atomic<CharType*>&> (data).exchange (newValue.data));
|
||||
}
|
||||
|
||||
private:
|
||||
CharType* data;
|
||||
};
|
||||
|
||||
} // namespace juce
|
567
modules/juce_core/text/juce_CharPointer_UTF8.h
Normal file
567
modules/juce_core/text/juce_CharPointer_UTF8.h
Normal file
@ -0,0 +1,567 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Wraps a pointer to a null-terminated UTF-8 character string, and provides
|
||||
various methods to operate on the data.
|
||||
@see CharPointer_UTF16, CharPointer_UTF32
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class CharPointer_UTF8 final
|
||||
{
|
||||
public:
|
||||
using CharType = char;
|
||||
|
||||
inline explicit CharPointer_UTF8 (const CharType* rawPointer) noexcept
|
||||
: data (const_cast<CharType*> (rawPointer))
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_UTF8 (const CharPointer_UTF8& other) noexcept
|
||||
: data (other.data)
|
||||
{
|
||||
}
|
||||
|
||||
inline CharPointer_UTF8 operator= (CharPointer_UTF8 other) noexcept
|
||||
{
|
||||
data = other.data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CharPointer_UTF8 operator= (const CharType* text) noexcept
|
||||
{
|
||||
data = const_cast<CharType*> (text);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** This is a pointer comparison, it doesn't compare the actual text. */
|
||||
inline bool operator== (CharPointer_UTF8 other) const noexcept { return data == other.data; }
|
||||
inline bool operator!= (CharPointer_UTF8 other) const noexcept { return data != other.data; }
|
||||
inline bool operator<= (CharPointer_UTF8 other) const noexcept { return data <= other.data; }
|
||||
inline bool operator< (CharPointer_UTF8 other) const noexcept { return data < other.data; }
|
||||
inline bool operator>= (CharPointer_UTF8 other) const noexcept { return data >= other.data; }
|
||||
inline bool operator> (CharPointer_UTF8 other) const noexcept { return data > other.data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline CharType* getAddress() const noexcept { return data; }
|
||||
|
||||
/** Returns the address that this pointer is pointing to. */
|
||||
inline operator const CharType*() const noexcept { return data; }
|
||||
|
||||
/** Returns true if this pointer is pointing to a null character. */
|
||||
inline bool isEmpty() const noexcept { return *data == 0; }
|
||||
|
||||
/** Returns true if this pointer is not pointing to a null character. */
|
||||
inline bool isNotEmpty() const noexcept { return *data != 0; }
|
||||
|
||||
/** Returns the unicode character that this pointer is pointing to. */
|
||||
juce_wchar operator*() const noexcept
|
||||
{
|
||||
auto byte = (signed char) *data;
|
||||
|
||||
if (byte >= 0)
|
||||
return (juce_wchar) (uint8) byte;
|
||||
|
||||
uint32 n = (uint32) (uint8) byte;
|
||||
uint32 mask = 0x7f;
|
||||
uint32 bit = 0x40;
|
||||
int numExtraValues = 0;
|
||||
|
||||
while ((n & bit) != 0 && bit > 0x8)
|
||||
{
|
||||
mask >>= 1;
|
||||
++numExtraValues;
|
||||
bit >>= 1;
|
||||
}
|
||||
|
||||
n &= mask;
|
||||
|
||||
for (int i = 1; i <= numExtraValues; ++i)
|
||||
{
|
||||
auto nextByte = (uint32) (uint8) data[i];
|
||||
|
||||
if ((nextByte & 0xc0) != 0x80)
|
||||
break;
|
||||
|
||||
n <<= 6;
|
||||
n |= (nextByte & 0x3f);
|
||||
}
|
||||
|
||||
return (juce_wchar) n;
|
||||
}
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
CharPointer_UTF8& operator++() noexcept
|
||||
{
|
||||
jassert (*data != 0); // trying to advance past the end of the string?
|
||||
auto n = (signed char) *data++;
|
||||
|
||||
if (n < 0)
|
||||
{
|
||||
uint8 bit = 0x40;
|
||||
|
||||
while ((static_cast<uint8> (n) & bit) != 0 && bit > 0x8)
|
||||
{
|
||||
++data;
|
||||
bit >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Moves this pointer back to the previous character in the string. */
|
||||
CharPointer_UTF8 operator--() noexcept
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while ((*--data & 0xc0) == 0x80 && ++count < 4)
|
||||
{}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns the character that this pointer is currently pointing to, and then
|
||||
advances the pointer to point to the next character. */
|
||||
juce_wchar getAndAdvance() noexcept
|
||||
{
|
||||
auto byte = (signed char) *data++;
|
||||
|
||||
if (byte >= 0)
|
||||
return (juce_wchar) (uint8) byte;
|
||||
|
||||
uint32 n = (uint32) (uint8) byte;
|
||||
uint32 mask = 0x7f;
|
||||
uint32 bit = 0x40;
|
||||
int numExtraValues = 0;
|
||||
|
||||
while ((n & bit) != 0 && bit > 0x8)
|
||||
{
|
||||
mask >>= 1;
|
||||
++numExtraValues;
|
||||
bit >>= 1;
|
||||
}
|
||||
|
||||
n &= mask;
|
||||
|
||||
while (--numExtraValues >= 0)
|
||||
{
|
||||
auto nextByte = (uint32) (uint8) *data;
|
||||
|
||||
if ((nextByte & 0xc0) != 0x80)
|
||||
break;
|
||||
|
||||
++data;
|
||||
n <<= 6;
|
||||
n |= (nextByte & 0x3f);
|
||||
}
|
||||
|
||||
return (juce_wchar) n;
|
||||
}
|
||||
|
||||
/** Moves this pointer along to the next character in the string. */
|
||||
CharPointer_UTF8 operator++ (int) noexcept
|
||||
{
|
||||
CharPointer_UTF8 temp (*this);
|
||||
++*this;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/** Moves this pointer forwards by the specified number of characters. */
|
||||
void operator+= (int numToSkip) noexcept
|
||||
{
|
||||
if (numToSkip < 0)
|
||||
{
|
||||
while (++numToSkip <= 0)
|
||||
--*this;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (--numToSkip >= 0)
|
||||
++*this;
|
||||
}
|
||||
}
|
||||
|
||||
/** Moves this pointer backwards by the specified number of characters. */
|
||||
void operator-= (int numToSkip) noexcept
|
||||
{
|
||||
operator+= (-numToSkip);
|
||||
}
|
||||
|
||||
/** Returns the character at a given character index from the start of the string. */
|
||||
juce_wchar operator[] (int characterIndex) const noexcept
|
||||
{
|
||||
auto p (*this);
|
||||
p += characterIndex;
|
||||
return *p;
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved forwards from this one by the specified number of characters. */
|
||||
CharPointer_UTF8 operator+ (int numToSkip) const noexcept
|
||||
{
|
||||
auto p (*this);
|
||||
p += numToSkip;
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Returns a pointer which is moved backwards from this one by the specified number of characters. */
|
||||
CharPointer_UTF8 operator- (int numToSkip) const noexcept
|
||||
{
|
||||
auto p (*this);
|
||||
p += -numToSkip;
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string. */
|
||||
size_t length() const noexcept
|
||||
{
|
||||
auto* d = data;
|
||||
size_t count = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto n = (uint32) (uint8) *d++;
|
||||
|
||||
if ((n & 0x80) != 0)
|
||||
{
|
||||
while ((*d & 0xc0) == 0x80)
|
||||
++d;
|
||||
}
|
||||
else if (n == 0)
|
||||
break;
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or the given value, whichever is lower. */
|
||||
size_t lengthUpTo (const size_t maxCharsToCount) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, maxCharsToCount);
|
||||
}
|
||||
|
||||
/** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */
|
||||
size_t lengthUpTo (const CharPointer_UTF8 end) const noexcept
|
||||
{
|
||||
return CharacterFunctions::lengthUpTo (*this, end);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that are used to represent this string.
|
||||
This includes the terminating null character.
|
||||
*/
|
||||
size_t sizeInBytes() const noexcept
|
||||
{
|
||||
jassert (data != nullptr);
|
||||
return strlen (data) + 1;
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
unicode character in this encoding format.
|
||||
*/
|
||||
static size_t getBytesRequiredFor (const juce_wchar charToWrite) noexcept
|
||||
{
|
||||
size_t num = 1;
|
||||
auto c = (uint32) charToWrite;
|
||||
|
||||
if (c >= 0x80)
|
||||
{
|
||||
++num;
|
||||
if (c >= 0x800)
|
||||
{
|
||||
++num;
|
||||
if (c >= 0x10000)
|
||||
++num;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that would be needed to represent the given
|
||||
string in this encoding format.
|
||||
The value returned does NOT include the terminating null character.
|
||||
*/
|
||||
template <class CharPointer>
|
||||
static size_t getBytesRequiredFor (CharPointer text) noexcept
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
while (auto n = text.getAndAdvance())
|
||||
count += getBytesRequiredFor (n);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Returns a pointer to the null character that terminates this string. */
|
||||
CharPointer_UTF8 findTerminatingNull() const noexcept
|
||||
{
|
||||
return CharPointer_UTF8 (data + strlen (data));
|
||||
}
|
||||
|
||||
/** Writes a unicode character to this string, and advances this pointer to point to the next position. */
|
||||
void write (const juce_wchar charToWrite) noexcept
|
||||
{
|
||||
auto c = (uint32) charToWrite;
|
||||
|
||||
if (c >= 0x80)
|
||||
{
|
||||
int numExtraBytes = 1;
|
||||
if (c >= 0x800)
|
||||
{
|
||||
++numExtraBytes;
|
||||
if (c >= 0x10000)
|
||||
++numExtraBytes;
|
||||
}
|
||||
|
||||
*data++ = (CharType) ((uint32) (0xff << (7 - numExtraBytes)) | (c >> (numExtraBytes * 6)));
|
||||
|
||||
while (--numExtraBytes >= 0)
|
||||
*data++ = (CharType) (0x80 | (0x3f & (c >> (numExtraBytes * 6))));
|
||||
}
|
||||
else
|
||||
{
|
||||
*data++ = (CharType) c;
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes a null character to this string (leaving the pointer's position unchanged). */
|
||||
inline void writeNull() const noexcept
|
||||
{
|
||||
*data = 0;
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes. */
|
||||
template <typename CharPointer>
|
||||
void writeAll (const CharPointer src) noexcept
|
||||
{
|
||||
CharacterFunctions::copyAll (*this, src);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes. */
|
||||
void writeAll (const CharPointer_UTF8 src) noexcept
|
||||
{
|
||||
auto* s = src.data;
|
||||
|
||||
while ((*data = *s) != 0)
|
||||
{
|
||||
++data;
|
||||
++s;
|
||||
}
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxDestBytes parameter specifies the maximum number of bytes that can be written
|
||||
to the destination buffer before stopping.
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept
|
||||
{
|
||||
return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes);
|
||||
}
|
||||
|
||||
/** Copies a source string to this pointer, advancing this pointer as it goes.
|
||||
The maxChars parameter specifies the maximum number of characters that can be
|
||||
written to the destination buffer before stopping (including the terminating null).
|
||||
*/
|
||||
template <typename CharPointer>
|
||||
void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept
|
||||
{
|
||||
CharacterFunctions::copyWithCharLimit (*this, src, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compare (const CharPointer other) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compare (*this, other);
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareUpTo (const CharPointer other, const int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCase (const CharPointer other) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCase (*this, other);
|
||||
}
|
||||
|
||||
/** Compares this string with another one. */
|
||||
int compareIgnoreCase (const CharPointer_UTF8 other) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCase (*this, other);
|
||||
}
|
||||
|
||||
/** Compares this string with another one, up to a specified number of characters. */
|
||||
template <typename CharPointer>
|
||||
int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept
|
||||
{
|
||||
return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars);
|
||||
}
|
||||
|
||||
/** Returns the character index of a substring, or -1 if it isn't found. */
|
||||
template <typename CharPointer>
|
||||
int indexOf (const CharPointer stringToFind) const noexcept
|
||||
{
|
||||
return CharacterFunctions::indexOf (*this, stringToFind);
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (const juce_wchar charToFind) const noexcept
|
||||
{
|
||||
return CharacterFunctions::indexOfChar (*this, charToFind);
|
||||
}
|
||||
|
||||
/** Returns the character index of a unicode character, or -1 if it isn't found. */
|
||||
int indexOf (const juce_wchar charToFind, const bool ignoreCase) const noexcept
|
||||
{
|
||||
return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind)
|
||||
: CharacterFunctions::indexOfChar (*this, charToFind);
|
||||
}
|
||||
|
||||
/** Returns true if the first character of this string is whitespace. */
|
||||
bool isWhitespace() const noexcept { const CharType c = *data; return c == ' ' || (c <= 13 && c >= 9); }
|
||||
/** Returns true if the first character of this string is a digit. */
|
||||
bool isDigit() const noexcept { const CharType c = *data; return c >= '0' && c <= '9'; }
|
||||
/** Returns true if the first character of this string is a letter. */
|
||||
bool isLetter() const noexcept { return CharacterFunctions::isLetter (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is a letter or digit. */
|
||||
bool isLetterOrDigit() const noexcept { return CharacterFunctions::isLetterOrDigit (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is upper-case. */
|
||||
bool isUpperCase() const noexcept { return CharacterFunctions::isUpperCase (operator*()) != 0; }
|
||||
/** Returns true if the first character of this string is lower-case. */
|
||||
bool isLowerCase() const noexcept { return CharacterFunctions::isLowerCase (operator*()) != 0; }
|
||||
|
||||
/** Returns an upper-case version of the first character of this string. */
|
||||
juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); }
|
||||
/** Returns a lower-case version of the first character of this string. */
|
||||
juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); }
|
||||
|
||||
/** Parses this string as a 32-bit integer. */
|
||||
int getIntValue32() const noexcept { return atoi (data); }
|
||||
|
||||
/** Parses this string as a 64-bit integer. */
|
||||
int64 getIntValue64() const noexcept
|
||||
{
|
||||
#if JUCE_WINDOWS && ! JUCE_MINGW
|
||||
return _atoi64 (data);
|
||||
#else
|
||||
return atoll (data);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Parses this string as a floating point double. */
|
||||
double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); }
|
||||
|
||||
/** Returns the first non-whitespace character in the string. */
|
||||
CharPointer_UTF8 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); }
|
||||
|
||||
/** Returns true if the given unicode character can be represented in this encoding. */
|
||||
static bool canRepresent (juce_wchar character) noexcept
|
||||
{
|
||||
return ((uint32) character) < (uint32) 0x10ffff;
|
||||
}
|
||||
|
||||
/** Returns true if this data contains a valid string in this encoding. */
|
||||
static bool isValidString (const CharType* dataToTest, int maxBytesToRead)
|
||||
{
|
||||
while (--maxBytesToRead >= 0 && *dataToTest != 0)
|
||||
{
|
||||
auto byte = (signed char) *dataToTest++;
|
||||
|
||||
if (byte < 0)
|
||||
{
|
||||
int bit = 0x40;
|
||||
int numExtraValues = 0;
|
||||
|
||||
while ((byte & bit) != 0)
|
||||
{
|
||||
if (bit < 8)
|
||||
return false;
|
||||
|
||||
++numExtraValues;
|
||||
bit >>= 1;
|
||||
|
||||
if (bit == 8 && (numExtraValues > maxBytesToRead
|
||||
|| *CharPointer_UTF8 (dataToTest - 1) > 0x10ffff))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numExtraValues == 0)
|
||||
return false;
|
||||
|
||||
maxBytesToRead -= numExtraValues;
|
||||
if (maxBytesToRead < 0)
|
||||
return false;
|
||||
|
||||
while (--numExtraValues >= 0)
|
||||
if ((*dataToTest++ & 0xc0) != 0x80)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Atomically swaps this pointer for a new value, returning the previous value. */
|
||||
CharPointer_UTF8 atomicSwap (const CharPointer_UTF8 newValue)
|
||||
{
|
||||
return CharPointer_UTF8 (reinterpret_cast<Atomic<CharType*>&> (data).exchange (newValue.data));
|
||||
}
|
||||
|
||||
/** These values are the byte-order mark (BOM) values for a UTF-8 stream. */
|
||||
enum
|
||||
{
|
||||
byteOrderMark1 = 0xef,
|
||||
byteOrderMark2 = 0xbb,
|
||||
byteOrderMark3 = 0xbf
|
||||
};
|
||||
|
||||
/** Returns true if the first three bytes in this pointer are the UTF8 byte-order mark (BOM).
|
||||
The pointer must not be null, and must point to at least 3 valid bytes.
|
||||
*/
|
||||
static bool isByteOrderMark (const void* possibleByteOrder) noexcept
|
||||
{
|
||||
jassert (possibleByteOrder != nullptr);
|
||||
auto c = static_cast<const uint8*> (possibleByteOrder);
|
||||
|
||||
return c[0] == (uint8) byteOrderMark1
|
||||
&& c[1] == (uint8) byteOrderMark2
|
||||
&& c[2] == (uint8) byteOrderMark3;
|
||||
}
|
||||
|
||||
private:
|
||||
CharType* data;
|
||||
};
|
||||
|
||||
} // namespace juce
|
287
modules/juce_core/text/juce_CharacterFunctions.cpp
Normal file
287
modules/juce_core/text/juce_CharacterFunctions.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 4514 4996)
|
||||
#endif
|
||||
|
||||
juce_wchar CharacterFunctions::toUpperCase (const juce_wchar character) noexcept
|
||||
{
|
||||
return (juce_wchar) towupper ((wint_t) character);
|
||||
}
|
||||
|
||||
juce_wchar CharacterFunctions::toLowerCase (const juce_wchar character) noexcept
|
||||
{
|
||||
return (juce_wchar) towlower ((wint_t) character);
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isUpperCase (const juce_wchar character) noexcept
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
return iswupper ((wint_t) character) != 0;
|
||||
#else
|
||||
return toLowerCase (character) != character;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isLowerCase (const juce_wchar character) noexcept
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
return iswlower ((wint_t) character) != 0;
|
||||
#else
|
||||
return toUpperCase (character) != character;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
bool CharacterFunctions::isWhitespace (const char character) noexcept
|
||||
{
|
||||
return character == ' ' || (character <= 13 && character >= 9);
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isWhitespace (const juce_wchar character) noexcept
|
||||
{
|
||||
return iswspace ((wint_t) character) != 0;
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isDigit (const char character) noexcept
|
||||
{
|
||||
return (character >= '0' && character <= '9');
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isDigit (const juce_wchar character) noexcept
|
||||
{
|
||||
return iswdigit ((wint_t) character) != 0;
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isLetter (const char character) noexcept
|
||||
{
|
||||
return (character >= 'a' && character <= 'z')
|
||||
|| (character >= 'A' && character <= 'Z');
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isLetter (const juce_wchar character) noexcept
|
||||
{
|
||||
return iswalpha ((wint_t) character) != 0;
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isLetterOrDigit (const char character) noexcept
|
||||
{
|
||||
return (character >= 'a' && character <= 'z')
|
||||
|| (character >= 'A' && character <= 'Z')
|
||||
|| (character >= '0' && character <= '9');
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) noexcept
|
||||
{
|
||||
return iswalnum ((wint_t) character) != 0;
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isPrintable (const char character) noexcept
|
||||
{
|
||||
return (character >= ' ' && character <= '~');
|
||||
}
|
||||
|
||||
bool CharacterFunctions::isPrintable (const juce_wchar character) noexcept
|
||||
{
|
||||
return iswprint ((wint_t) character) != 0;
|
||||
}
|
||||
|
||||
int CharacterFunctions::getHexDigitValue (const juce_wchar digit) noexcept
|
||||
{
|
||||
auto d = (unsigned int) (digit - '0');
|
||||
|
||||
if (d < (unsigned int) 10)
|
||||
return (int) d;
|
||||
|
||||
d += (unsigned int) ('0' - 'a');
|
||||
|
||||
if (d < (unsigned int) 6)
|
||||
return (int) d + 10;
|
||||
|
||||
d += (unsigned int) ('a' - 'A');
|
||||
|
||||
if (d < (unsigned int) 6)
|
||||
return (int) d + 10;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept
|
||||
{
|
||||
if (exponent == 0)
|
||||
return value;
|
||||
|
||||
if (value == 0.0)
|
||||
return 0;
|
||||
|
||||
const bool negative = (exponent < 0);
|
||||
|
||||
if (negative)
|
||||
exponent = -exponent;
|
||||
|
||||
double result = 1.0, power = 10.0;
|
||||
|
||||
for (int bit = 1; exponent != 0; bit <<= 1)
|
||||
{
|
||||
if ((exponent & bit) != 0)
|
||||
{
|
||||
exponent ^= bit;
|
||||
result *= power;
|
||||
|
||||
if (exponent == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
power *= power;
|
||||
}
|
||||
|
||||
return negative ? (value / result) : (value * result);
|
||||
}
|
||||
|
||||
juce_wchar CharacterFunctions::getUnicodeCharFromWindows1252Codepage (const uint8 c) noexcept
|
||||
{
|
||||
if (c < 0x80 || c >= 0xa0)
|
||||
return (juce_wchar) c;
|
||||
|
||||
static const uint16 lookup[] = { 0x20AC, 0x0007, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
|
||||
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0007, 0x017D, 0x0007,
|
||||
0x0007, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
|
||||
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0007, 0x017E, 0x0178 };
|
||||
|
||||
return (juce_wchar) lookup[c - 0x80];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
#define QUOTE(x) #x
|
||||
#define STR(value) QUOTE(value)
|
||||
#define ASYM_STRING_DOUBLE_PAIR(str, value) std::pair<String, double> (STR(str), value)
|
||||
#define STRING_DOUBLE_PAIR(value) ASYM_STRING_DOUBLE_PAIR(value, value)
|
||||
#define STRING_DOUBLE_PAIR_COMBOS(value) \
|
||||
STRING_DOUBLE_PAIR(value), \
|
||||
STRING_DOUBLE_PAIR(-value), \
|
||||
ASYM_STRING_DOUBLE_PAIR(+value, value), \
|
||||
ASYM_STRING_DOUBLE_PAIR(000000 ## value, value), \
|
||||
ASYM_STRING_DOUBLE_PAIR(+000 ## value, value), \
|
||||
ASYM_STRING_DOUBLE_PAIR(-0 ## value, -value)
|
||||
|
||||
class CharacterFunctionsTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
CharacterFunctionsTests() : UnitTest ("CharacterFunctions", "Text") {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("readDoubleValue");
|
||||
|
||||
static const std::pair<String, double> testValues[] =
|
||||
{
|
||||
// Integers
|
||||
STRING_DOUBLE_PAIR_COMBOS (0),
|
||||
STRING_DOUBLE_PAIR_COMBOS (3),
|
||||
STRING_DOUBLE_PAIR_COMBOS (4931),
|
||||
STRING_DOUBLE_PAIR_COMBOS (5000),
|
||||
STRING_DOUBLE_PAIR_COMBOS (9862097),
|
||||
|
||||
// Floating point numbers
|
||||
STRING_DOUBLE_PAIR_COMBOS (7.000),
|
||||
STRING_DOUBLE_PAIR_COMBOS (0.2),
|
||||
STRING_DOUBLE_PAIR_COMBOS (.298630),
|
||||
STRING_DOUBLE_PAIR_COMBOS (1.118),
|
||||
STRING_DOUBLE_PAIR_COMBOS (0.9000),
|
||||
STRING_DOUBLE_PAIR_COMBOS (0.0000001),
|
||||
STRING_DOUBLE_PAIR_COMBOS (500.0000001),
|
||||
STRING_DOUBLE_PAIR_COMBOS (9862098.2398604),
|
||||
|
||||
// Exponents
|
||||
STRING_DOUBLE_PAIR_COMBOS (0e0),
|
||||
STRING_DOUBLE_PAIR_COMBOS (0.e0),
|
||||
STRING_DOUBLE_PAIR_COMBOS (0.00000e0),
|
||||
STRING_DOUBLE_PAIR_COMBOS (.0e7),
|
||||
STRING_DOUBLE_PAIR_COMBOS (0e-5),
|
||||
STRING_DOUBLE_PAIR_COMBOS (2E0),
|
||||
STRING_DOUBLE_PAIR_COMBOS (4.E0),
|
||||
STRING_DOUBLE_PAIR_COMBOS (1.2000000E0),
|
||||
STRING_DOUBLE_PAIR_COMBOS (1.2000000E6),
|
||||
STRING_DOUBLE_PAIR_COMBOS (.398e3),
|
||||
STRING_DOUBLE_PAIR_COMBOS (10e10),
|
||||
STRING_DOUBLE_PAIR_COMBOS (1.4962e+2),
|
||||
STRING_DOUBLE_PAIR_COMBOS (3198693.0973e4),
|
||||
STRING_DOUBLE_PAIR_COMBOS (10973097.2087e-4),
|
||||
STRING_DOUBLE_PAIR_COMBOS (1.3986e00006),
|
||||
STRING_DOUBLE_PAIR_COMBOS (2087.3087e+00006),
|
||||
STRING_DOUBLE_PAIR_COMBOS (6.0872e-00006),
|
||||
|
||||
// Too many sig figs. The parsing routine on MinGW gets the last
|
||||
// significant figure wrong.
|
||||
STRING_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890),
|
||||
STRING_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890e-111)
|
||||
|
||||
// Limits. DBL_MAX may not exist on Linux.
|
||||
#if ! JUCE_LINUX
|
||||
, STRING_DOUBLE_PAIR (DBL_MAX),
|
||||
STRING_DOUBLE_PAIR (-DBL_MAX),
|
||||
STRING_DOUBLE_PAIR (DBL_MIN)
|
||||
#endif
|
||||
};
|
||||
|
||||
for (auto trial : testValues)
|
||||
{
|
||||
auto charPtr = trial.first.getCharPointer();
|
||||
expectEquals (CharacterFunctions::readDoubleValue (charPtr), trial.second);
|
||||
}
|
||||
|
||||
{
|
||||
String nans[] = { "NaN", "-nan", "+NAN", "1.0E1024", "-1.0E-999", "1.23456789012345678901234567890e123456789"};
|
||||
for (auto nan : nans)
|
||||
{
|
||||
auto charPtr = nan.getCharPointer();
|
||||
expect (std::isnan (CharacterFunctions::readDoubleValue (charPtr)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
String infs[] = { "Inf", "-inf", "INF"};
|
||||
for (auto inf : infs)
|
||||
{
|
||||
auto charPtr = inf.getCharPointer();
|
||||
expect (std::isinf (CharacterFunctions::readDoubleValue (charPtr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static CharacterFunctionsTests characterFunctionsTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
763
modules/juce_core/text/juce_CharacterFunctions.h
Normal file
763
modules/juce_core/text/juce_CharacterFunctions.h
Normal file
@ -0,0 +1,763 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_WINDOWS && ! DOXYGEN
|
||||
#define JUCE_NATIVE_WCHAR_IS_UTF8 0
|
||||
#define JUCE_NATIVE_WCHAR_IS_UTF16 1
|
||||
#define JUCE_NATIVE_WCHAR_IS_UTF32 0
|
||||
#else
|
||||
/** This macro will be set to 1 if the compiler's native wchar_t is an 8-bit type. */
|
||||
#define JUCE_NATIVE_WCHAR_IS_UTF8 0
|
||||
/** This macro will be set to 1 if the compiler's native wchar_t is a 16-bit type. */
|
||||
#define JUCE_NATIVE_WCHAR_IS_UTF16 0
|
||||
/** This macro will be set to 1 if the compiler's native wchar_t is a 32-bit type. */
|
||||
#define JUCE_NATIVE_WCHAR_IS_UTF32 1
|
||||
#endif
|
||||
|
||||
#if JUCE_NATIVE_WCHAR_IS_UTF32 || DOXYGEN
|
||||
/** A platform-independent 32-bit unicode character type. */
|
||||
using juce_wchar = wchar_t;
|
||||
#else
|
||||
using juce_wchar = uint32;
|
||||
#endif
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/** This macro is deprecated, but preserved for compatibility with old code. */
|
||||
#define JUCE_T(stringLiteral) (L##stringLiteral)
|
||||
#endif
|
||||
|
||||
#if JUCE_DEFINE_T_MACRO
|
||||
/** The 'T' macro is an alternative for using the "L" prefix in front of a string literal.
|
||||
|
||||
This macro is deprecated, but available for compatibility with old code if you set
|
||||
JUCE_DEFINE_T_MACRO = 1. The fastest, most portable and best way to write your string
|
||||
literals is as standard char strings, using escaped utf-8 character sequences for extended
|
||||
characters, rather than trying to store them as wide-char strings.
|
||||
*/
|
||||
#define T(stringLiteral) JUCE_T(stringLiteral)
|
||||
#endif
|
||||
|
||||
#if ! DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
// GNU libstdc++ does not have std::make_unsigned
|
||||
namespace internal
|
||||
{
|
||||
template <typename Type> struct make_unsigned { using type = Type; };
|
||||
template <> struct make_unsigned<signed char> { using type = unsigned char; };
|
||||
template <> struct make_unsigned<char> { using type = unsigned char; };
|
||||
template <> struct make_unsigned<short> { using type = unsigned short; };
|
||||
template <> struct make_unsigned<int> { using type = unsigned int; };
|
||||
template <> struct make_unsigned<long> { using type = unsigned long; };
|
||||
template <> struct make_unsigned<long long> { using type = unsigned long long; };
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A collection of functions for manipulating characters and character strings.
|
||||
|
||||
Most of these methods are designed for internal use by the String and CharPointer
|
||||
classes, but some of them may be useful to call directly.
|
||||
|
||||
@see String, CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API CharacterFunctions
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Converts a character to upper-case. */
|
||||
static juce_wchar toUpperCase (juce_wchar character) noexcept;
|
||||
/** Converts a character to lower-case. */
|
||||
static juce_wchar toLowerCase (juce_wchar character) noexcept;
|
||||
|
||||
/** Checks whether a unicode character is upper-case. */
|
||||
static bool isUpperCase (juce_wchar character) noexcept;
|
||||
/** Checks whether a unicode character is lower-case. */
|
||||
static bool isLowerCase (juce_wchar character) noexcept;
|
||||
|
||||
/** Checks whether a character is whitespace. */
|
||||
static bool isWhitespace (char character) noexcept;
|
||||
/** Checks whether a character is whitespace. */
|
||||
static bool isWhitespace (juce_wchar character) noexcept;
|
||||
|
||||
/** Checks whether a character is a digit. */
|
||||
static bool isDigit (char character) noexcept;
|
||||
/** Checks whether a character is a digit. */
|
||||
static bool isDigit (juce_wchar character) noexcept;
|
||||
|
||||
/** Checks whether a character is alphabetic. */
|
||||
static bool isLetter (char character) noexcept;
|
||||
/** Checks whether a character is alphabetic. */
|
||||
static bool isLetter (juce_wchar character) noexcept;
|
||||
|
||||
/** Checks whether a character is alphabetic or numeric. */
|
||||
static bool isLetterOrDigit (char character) noexcept;
|
||||
/** Checks whether a character is alphabetic or numeric. */
|
||||
static bool isLetterOrDigit (juce_wchar character) noexcept;
|
||||
|
||||
/** Checks whether a character is a printable character, i.e. alphabetic, numeric,
|
||||
a punctuation character or a space.
|
||||
*/
|
||||
static bool isPrintable (char character) noexcept;
|
||||
|
||||
/** Checks whether a character is a printable character, i.e. alphabetic, numeric,
|
||||
a punctuation character or a space.
|
||||
*/
|
||||
static bool isPrintable (juce_wchar character) noexcept;
|
||||
|
||||
/** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */
|
||||
static int getHexDigitValue (juce_wchar digit) noexcept;
|
||||
|
||||
/** Converts a byte of Windows 1252 codepage to unicode. */
|
||||
static juce_wchar getUnicodeCharFromWindows1252Codepage (uint8 windows1252Char) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Parses a character string to read a floating-point number.
|
||||
Note that this will advance the pointer that is passed in, leaving it at
|
||||
the end of the number.
|
||||
*/
|
||||
template <typename CharPointerType>
|
||||
static double readDoubleValue (CharPointerType& text) noexcept
|
||||
{
|
||||
#if JUCE_MINGW
|
||||
bool isNegative = false;
|
||||
#else
|
||||
JUCE_CONSTEXPR const int maxSignificantDigits = 17 + 1; // An additional digit for rounding
|
||||
JUCE_CONSTEXPR const int bufferSize = maxSignificantDigits + 7 + 1; // -.E-XXX and a trailing null-terminator
|
||||
char buffer[bufferSize] = {};
|
||||
char* currentCharacter = &(buffer[0]);
|
||||
#endif
|
||||
|
||||
text = text.findEndOfWhitespace();
|
||||
auto c = *text;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '-':
|
||||
#if JUCE_MINGW
|
||||
isNegative = true;
|
||||
#else
|
||||
*currentCharacter++ = '-';
|
||||
#endif
|
||||
// Fall-through..
|
||||
case '+':
|
||||
c = *++text;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
case 'N':
|
||||
if ((text[1] == 'a' || text[1] == 'A') && (text[2] == 'n' || text[2] == 'N'))
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
case 'I':
|
||||
if ((text[1] == 'n' || text[1] == 'N') && (text[2] == 'f' || text[2] == 'F'))
|
||||
return std::numeric_limits<double>::infinity();
|
||||
break;
|
||||
}
|
||||
|
||||
#if JUCE_MINGW
|
||||
// MinGW does not have access to the locale functions required for strtold, so we parse the doubles
|
||||
// ourselves. There are some edge cases where the least significant digit will be wrong!
|
||||
double result[3] = { 0 }, accumulator[2] = { 0 };
|
||||
int exponentAdjustment[2] = { 0 }, exponentAccumulator[2] = { -1, -1 };
|
||||
int exponent = 0, decPointIndex = 0, digit = 0;
|
||||
int lastDigit = 0, numSignificantDigits = 0;
|
||||
bool digitsFound = false;
|
||||
JUCE_CONSTEXPR const int maxSignificantDigits = 17 + 1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (text.isDigit())
|
||||
{
|
||||
lastDigit = digit;
|
||||
digit = (int) text.getAndAdvance() - '0';
|
||||
digitsFound = true;
|
||||
|
||||
if (decPointIndex != 0)
|
||||
exponentAdjustment[1]++;
|
||||
|
||||
if (numSignificantDigits == 0 && digit == 0)
|
||||
continue;
|
||||
|
||||
if (++numSignificantDigits > maxSignificantDigits)
|
||||
{
|
||||
if (digit > 5)
|
||||
++accumulator [decPointIndex];
|
||||
else if (digit == 5 && (lastDigit & 1) != 0)
|
||||
++accumulator [decPointIndex];
|
||||
|
||||
if (decPointIndex > 0)
|
||||
exponentAdjustment[1]--;
|
||||
else
|
||||
exponentAdjustment[0]++;
|
||||
|
||||
while (text.isDigit())
|
||||
{
|
||||
++text;
|
||||
if (decPointIndex == 0)
|
||||
exponentAdjustment[0]++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto maxAccumulatorValue = (double) ((std::numeric_limits<unsigned int>::max() - 9) / 10);
|
||||
if (accumulator [decPointIndex] > maxAccumulatorValue)
|
||||
{
|
||||
result [decPointIndex] = mulexp10 (result [decPointIndex], exponentAccumulator [decPointIndex])
|
||||
+ accumulator [decPointIndex];
|
||||
accumulator [decPointIndex] = 0;
|
||||
exponentAccumulator [decPointIndex] = 0;
|
||||
}
|
||||
|
||||
accumulator [decPointIndex] = accumulator[decPointIndex] * 10 + digit;
|
||||
exponentAccumulator [decPointIndex]++;
|
||||
}
|
||||
}
|
||||
else if (decPointIndex == 0 && *text == '.')
|
||||
{
|
||||
++text;
|
||||
decPointIndex = 1;
|
||||
|
||||
if (numSignificantDigits > maxSignificantDigits)
|
||||
{
|
||||
while (text.isDigit())
|
||||
++text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result[0] = mulexp10 (result[0], exponentAccumulator[0]) + accumulator[0];
|
||||
|
||||
if (decPointIndex != 0)
|
||||
result[1] = mulexp10 (result[1], exponentAccumulator[1]) + accumulator[1];
|
||||
|
||||
c = *text;
|
||||
if ((c == 'e' || c == 'E') && digitsFound)
|
||||
{
|
||||
auto negativeExponent = false;
|
||||
|
||||
switch (*++text)
|
||||
{
|
||||
case '-': negativeExponent = true; // fall-through..
|
||||
case '+': ++text;
|
||||
}
|
||||
|
||||
while (text.isDigit())
|
||||
exponent = (exponent * 10) + ((int) text.getAndAdvance() - '0');
|
||||
|
||||
if (negativeExponent)
|
||||
exponent = -exponent;
|
||||
}
|
||||
|
||||
auto r = mulexp10 (result[0], exponent + exponentAdjustment[0]);
|
||||
if (decPointIndex != 0)
|
||||
r += mulexp10 (result[1], exponent - exponentAdjustment[1]);
|
||||
|
||||
return isNegative ? -r : r;
|
||||
|
||||
#else // ! JUCE_MINGW
|
||||
|
||||
int numSigFigs = 0;
|
||||
bool decimalPointFound = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (text.isDigit())
|
||||
{
|
||||
auto digit = (int) text.getAndAdvance() - '0';
|
||||
|
||||
if (numSigFigs >= maxSignificantDigits
|
||||
|| ((numSigFigs == 0 && (! decimalPointFound)) && digit == 0))
|
||||
continue;
|
||||
|
||||
*currentCharacter++ = (char) ('0' + (char) digit);
|
||||
numSigFigs++;
|
||||
}
|
||||
else if ((! decimalPointFound) && *text == '.')
|
||||
{
|
||||
++text;
|
||||
*currentCharacter++ = '.';
|
||||
decimalPointFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c = *text;
|
||||
|
||||
if ((c == 'e' || c == 'E') && numSigFigs > 0)
|
||||
{
|
||||
*currentCharacter++ = 'e';
|
||||
|
||||
switch (*++text)
|
||||
{
|
||||
case '-': *currentCharacter++ = '-'; // Fall-through..
|
||||
case '+': ++text;
|
||||
}
|
||||
|
||||
int exponentMagnitude = 0;
|
||||
|
||||
while (text.isDigit())
|
||||
{
|
||||
if (currentCharacter == &buffer[bufferSize - 1])
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
auto digit = (int) text.getAndAdvance() - '0';
|
||||
|
||||
if (digit != 0 || exponentMagnitude != 0)
|
||||
{
|
||||
*currentCharacter++ = (char) ('0' + (char) digit);
|
||||
exponentMagnitude = (exponentMagnitude * 10) + digit;
|
||||
}
|
||||
}
|
||||
|
||||
if (exponentMagnitude > std::numeric_limits<double>::max_exponent10)
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
if (exponentMagnitude == 0)
|
||||
*currentCharacter++ = '0';
|
||||
}
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
static _locale_t locale = _create_locale (LC_ALL, "C");
|
||||
return _strtod_l (&buffer[0], nullptr, locale);
|
||||
#else
|
||||
static locale_t locale = newlocale (LC_ALL_MASK, "C", nullptr);
|
||||
#if JUCE_ANDROID
|
||||
return (double) strtold_l (&buffer[0], nullptr, locale);
|
||||
#else
|
||||
return strtod_l (&buffer[0], nullptr, locale);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // JUCE_MINGW
|
||||
}
|
||||
|
||||
/** Parses a character string, to read a floating-point value. */
|
||||
template <typename CharPointerType>
|
||||
static double getDoubleValue (CharPointerType text) noexcept
|
||||
{
|
||||
return readDoubleValue (text);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Parses a character string, to read an integer value. */
|
||||
template <typename IntType, typename CharPointerType>
|
||||
static IntType getIntValue (const CharPointerType text) noexcept
|
||||
{
|
||||
using UIntType = typename internal::make_unsigned<IntType>::type;
|
||||
|
||||
UIntType v = 0;
|
||||
auto s = text.findEndOfWhitespace();
|
||||
const bool isNeg = *s == '-';
|
||||
|
||||
if (isNeg)
|
||||
++s;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = s.getAndAdvance();
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
v = v * 10 + (UIntType) (c - '0');
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return isNeg ? - (IntType) v : (IntType) v;
|
||||
}
|
||||
|
||||
/** Parses a character string, to read a hexadecimal value. */
|
||||
template <typename ResultType>
|
||||
struct HexParser
|
||||
{
|
||||
template <typename CharPointerType>
|
||||
static ResultType parse (CharPointerType t) noexcept
|
||||
{
|
||||
ResultType result = 0;
|
||||
|
||||
while (! t.isEmpty())
|
||||
{
|
||||
auto hexValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance());
|
||||
|
||||
if (hexValue >= 0)
|
||||
result = (result << 4) | hexValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Counts the number of characters in a given string, stopping if the count exceeds
|
||||
a specified limit. */
|
||||
template <typename CharPointerType>
|
||||
static size_t lengthUpTo (CharPointerType text, const size_t maxCharsToCount) noexcept
|
||||
{
|
||||
size_t len = 0;
|
||||
|
||||
while (len < maxCharsToCount && text.getAndAdvance() != 0)
|
||||
++len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/** Counts the number of characters in a given string, stopping if the count exceeds
|
||||
a specified end-pointer. */
|
||||
template <typename CharPointerType>
|
||||
static size_t lengthUpTo (CharPointerType start, const CharPointerType end) noexcept
|
||||
{
|
||||
size_t len = 0;
|
||||
|
||||
while (start < end && start.getAndAdvance() != 0)
|
||||
++len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/** Copies null-terminated characters from one string to another. */
|
||||
template <typename DestCharPointerType, typename SrcCharPointerType>
|
||||
static void copyAll (DestCharPointerType& dest, SrcCharPointerType src) noexcept
|
||||
{
|
||||
while (auto c = src.getAndAdvance())
|
||||
dest.write (c);
|
||||
|
||||
dest.writeNull();
|
||||
}
|
||||
|
||||
/** Copies characters from one string to another, up to a null terminator
|
||||
or a given byte size limit. */
|
||||
template <typename DestCharPointerType, typename SrcCharPointerType>
|
||||
static size_t copyWithDestByteLimit (DestCharPointerType& dest, SrcCharPointerType src, size_t maxBytesToWrite) noexcept
|
||||
{
|
||||
auto startAddress = dest.getAddress();
|
||||
auto maxBytes = (ssize_t) maxBytesToWrite;
|
||||
maxBytes -= sizeof (typename DestCharPointerType::CharType); // (allow for a terminating null)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = src.getAndAdvance();
|
||||
auto bytesNeeded = DestCharPointerType::getBytesRequiredFor (c);
|
||||
maxBytes -= bytesNeeded;
|
||||
|
||||
if (c == 0 || maxBytes < 0)
|
||||
break;
|
||||
|
||||
dest.write (c);
|
||||
}
|
||||
|
||||
dest.writeNull();
|
||||
|
||||
return (size_t) getAddressDifference (dest.getAddress(), startAddress)
|
||||
+ sizeof (typename DestCharPointerType::CharType);
|
||||
}
|
||||
|
||||
/** Copies characters from one string to another, up to a null terminator
|
||||
or a given maximum number of characters. */
|
||||
template <typename DestCharPointerType, typename SrcCharPointerType>
|
||||
static void copyWithCharLimit (DestCharPointerType& dest, SrcCharPointerType src, int maxChars) noexcept
|
||||
{
|
||||
while (--maxChars > 0)
|
||||
{
|
||||
auto c = src.getAndAdvance();
|
||||
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
dest.write (c);
|
||||
}
|
||||
|
||||
dest.writeNull();
|
||||
}
|
||||
|
||||
/** Compares two characters. */
|
||||
static inline int compare (juce_wchar char1, juce_wchar char2) noexcept
|
||||
{
|
||||
if (auto diff = static_cast<int> (char1) - static_cast<int> (char2))
|
||||
return diff < 0 ? -1 : 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Compares two null-terminated character strings. */
|
||||
template <typename CharPointerType1, typename CharPointerType2>
|
||||
static int compare (CharPointerType1 s1, CharPointerType2 s2) noexcept
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
auto c1 = s1.getAndAdvance();
|
||||
|
||||
if (auto diff = compare (c1, s2.getAndAdvance()))
|
||||
return diff;
|
||||
|
||||
if (c1 == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Compares two null-terminated character strings, up to a given number of characters. */
|
||||
template <typename CharPointerType1, typename CharPointerType2>
|
||||
static int compareUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept
|
||||
{
|
||||
while (--maxChars >= 0)
|
||||
{
|
||||
auto c1 = s1.getAndAdvance();
|
||||
|
||||
if (auto diff = compare (c1, s2.getAndAdvance()))
|
||||
return diff;
|
||||
|
||||
if (c1 == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Compares two characters, using a case-independant match. */
|
||||
static inline int compareIgnoreCase (juce_wchar char1, juce_wchar char2) noexcept
|
||||
{
|
||||
return char1 != char2 ? compare (toUpperCase (char1), toUpperCase (char2)) : 0;
|
||||
}
|
||||
|
||||
/** Compares two null-terminated character strings, using a case-independant match. */
|
||||
template <typename CharPointerType1, typename CharPointerType2>
|
||||
static int compareIgnoreCase (CharPointerType1 s1, CharPointerType2 s2) noexcept
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
auto c1 = s1.getAndAdvance();
|
||||
|
||||
if (auto diff = compareIgnoreCase (c1, s2.getAndAdvance()))
|
||||
return diff;
|
||||
|
||||
if (c1 == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Compares two null-terminated character strings, using a case-independent match. */
|
||||
template <typename CharPointerType1, typename CharPointerType2>
|
||||
static int compareIgnoreCaseUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept
|
||||
{
|
||||
while (--maxChars >= 0)
|
||||
{
|
||||
auto c1 = s1.getAndAdvance();
|
||||
|
||||
if (auto diff = compareIgnoreCase (c1, s2.getAndAdvance()))
|
||||
return diff;
|
||||
|
||||
if (c1 == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Finds the character index of a given substring in another string.
|
||||
Returns -1 if the substring is not found.
|
||||
*/
|
||||
template <typename CharPointerType1, typename CharPointerType2>
|
||||
static int indexOf (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept
|
||||
{
|
||||
int index = 0;
|
||||
auto substringLength = (int) substringToLookFor.length();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (textToSearch.compareUpTo (substringToLookFor, substringLength) == 0)
|
||||
return index;
|
||||
|
||||
if (textToSearch.getAndAdvance() == 0)
|
||||
return -1;
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a pointer to the first occurrence of a substring in a string.
|
||||
If the substring is not found, this will return a pointer to the string's
|
||||
null terminator.
|
||||
*/
|
||||
template <typename CharPointerType1, typename CharPointerType2>
|
||||
static CharPointerType1 find (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept
|
||||
{
|
||||
auto substringLength = (int) substringToLookFor.length();
|
||||
|
||||
while (textToSearch.compareUpTo (substringToLookFor, substringLength) != 0
|
||||
&& ! textToSearch.isEmpty())
|
||||
++textToSearch;
|
||||
|
||||
return textToSearch;
|
||||
}
|
||||
|
||||
/** Returns a pointer to the first occurrence of a substring in a string.
|
||||
If the substring is not found, this will return a pointer to the string's
|
||||
null terminator.
|
||||
*/
|
||||
template <typename CharPointerType>
|
||||
static CharPointerType find (CharPointerType textToSearch, const juce_wchar charToLookFor) noexcept
|
||||
{
|
||||
for (;; ++textToSearch)
|
||||
{
|
||||
auto c = *textToSearch;
|
||||
|
||||
if (c == charToLookFor || c == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return textToSearch;
|
||||
}
|
||||
|
||||
/** Finds the character index of a given substring in another string, using
|
||||
a case-independent match.
|
||||
Returns -1 if the substring is not found.
|
||||
*/
|
||||
template <typename CharPointerType1, typename CharPointerType2>
|
||||
static int indexOfIgnoreCase (CharPointerType1 haystack, const CharPointerType2 needle) noexcept
|
||||
{
|
||||
int index = 0;
|
||||
auto needleLength = (int) needle.length();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (haystack.compareIgnoreCaseUpTo (needle, needleLength) == 0)
|
||||
return index;
|
||||
|
||||
if (haystack.getAndAdvance() == 0)
|
||||
return -1;
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
/** Finds the character index of a given character in another string.
|
||||
Returns -1 if the character is not found.
|
||||
*/
|
||||
template <typename Type>
|
||||
static int indexOfChar (Type text, const juce_wchar charToFind) noexcept
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (! text.isEmpty())
|
||||
{
|
||||
if (text.getAndAdvance() == charToFind)
|
||||
return i;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Finds the character index of a given character in another string, using
|
||||
a case-independent match.
|
||||
Returns -1 if the character is not found.
|
||||
*/
|
||||
template <typename Type>
|
||||
static int indexOfCharIgnoreCase (Type text, juce_wchar charToFind) noexcept
|
||||
{
|
||||
charToFind = CharacterFunctions::toLowerCase (charToFind);
|
||||
int i = 0;
|
||||
|
||||
while (! text.isEmpty())
|
||||
{
|
||||
if (text.toLowerCase() == charToFind)
|
||||
return i;
|
||||
|
||||
++text;
|
||||
++i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Returns a pointer to the first non-whitespace character in a string.
|
||||
If the string contains only whitespace, this will return a pointer
|
||||
to its null terminator.
|
||||
*/
|
||||
template <typename Type>
|
||||
static Type findEndOfWhitespace (Type text) noexcept
|
||||
{
|
||||
while (text.isWhitespace())
|
||||
++text;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/** Returns a pointer to the first character in the string which is found in
|
||||
the breakCharacters string.
|
||||
*/
|
||||
template <typename Type, typename BreakType>
|
||||
static Type findEndOfToken (Type text, BreakType breakCharacters, Type quoteCharacters)
|
||||
{
|
||||
juce_wchar currentQuoteChar = 0;
|
||||
|
||||
while (! text.isEmpty())
|
||||
{
|
||||
auto c = text.getAndAdvance();
|
||||
|
||||
if (currentQuoteChar == 0 && breakCharacters.indexOf (c) >= 0)
|
||||
{
|
||||
--text;
|
||||
break;
|
||||
}
|
||||
|
||||
if (quoteCharacters.indexOf (c) >= 0)
|
||||
{
|
||||
if (currentQuoteChar == 0)
|
||||
currentQuoteChar = c;
|
||||
else if (currentQuoteChar == c)
|
||||
currentQuoteChar = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private:
|
||||
static double mulexp10 (double value, int exponent) noexcept;
|
||||
};
|
||||
|
||||
} // namespace juce
|
74
modules/juce_core/text/juce_Identifier.cpp
Normal file
74
modules/juce_core/text/juce_Identifier.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
Identifier::Identifier() noexcept {}
|
||||
Identifier::~Identifier() noexcept {}
|
||||
|
||||
Identifier::Identifier (const Identifier& other) noexcept : name (other.name) {}
|
||||
|
||||
Identifier::Identifier (Identifier&& other) noexcept : name (static_cast<String&&> (other.name)) {}
|
||||
|
||||
Identifier& Identifier::operator= (Identifier&& other) noexcept
|
||||
{
|
||||
name = static_cast<String&&> (other.name);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Identifier& Identifier::operator= (const Identifier& other) noexcept
|
||||
{
|
||||
name = other.name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Identifier::Identifier (const String& nm)
|
||||
: name (StringPool::getGlobalPool().getPooledString (nm))
|
||||
{
|
||||
// An Identifier cannot be created from an empty string!
|
||||
jassert (nm.isNotEmpty());
|
||||
}
|
||||
|
||||
Identifier::Identifier (const char* nm)
|
||||
: name (StringPool::getGlobalPool().getPooledString (nm))
|
||||
{
|
||||
// An Identifier cannot be created from an empty string!
|
||||
jassert (nm != nullptr && nm[0] != 0);
|
||||
}
|
||||
|
||||
Identifier::Identifier (String::CharPointerType start, String::CharPointerType end)
|
||||
: name (StringPool::getGlobalPool().getPooledString (start, end))
|
||||
{
|
||||
// An Identifier cannot be created from an empty string!
|
||||
jassert (start < end);
|
||||
}
|
||||
|
||||
Identifier Identifier::null;
|
||||
|
||||
bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept
|
||||
{
|
||||
return possibleIdentifier.isNotEmpty()
|
||||
&& possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:#@$%");
|
||||
}
|
||||
|
||||
} // namespace juce
|
132
modules/juce_core/text/juce_Identifier.h
Normal file
132
modules/juce_core/text/juce_Identifier.h
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a string identifier, designed for accessing properties by name.
|
||||
|
||||
Comparing two Identifier objects is very fast (an O(1) operation), but creating
|
||||
them can be slower than just using a String directly, so the optimal way to use them
|
||||
is to keep some static Identifier objects for the things you use often.
|
||||
|
||||
@see NamedValueSet, ValueTree
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API Identifier final
|
||||
{
|
||||
public:
|
||||
/** Creates a null identifier. */
|
||||
Identifier() noexcept;
|
||||
|
||||
/** Creates an identifier with a specified name.
|
||||
Because this name may need to be used in contexts such as script variables or XML
|
||||
tags, it must only contain ascii letters and digits, or the underscore character.
|
||||
*/
|
||||
Identifier (const char* name);
|
||||
|
||||
/** Creates an identifier with a specified name.
|
||||
Because this name may need to be used in contexts such as script variables or XML
|
||||
tags, it must only contain ascii letters and digits, or the underscore character.
|
||||
*/
|
||||
Identifier (const String& name);
|
||||
|
||||
/** Creates an identifier with a specified name.
|
||||
Because this name may need to be used in contexts such as script variables or XML
|
||||
tags, it must only contain ascii letters and digits, or the underscore character.
|
||||
*/
|
||||
Identifier (String::CharPointerType nameStart, String::CharPointerType nameEnd);
|
||||
|
||||
/** Creates a copy of another identifier. */
|
||||
Identifier (const Identifier& other) noexcept;
|
||||
|
||||
/** Creates a copy of another identifier. */
|
||||
Identifier& operator= (const Identifier& other) noexcept;
|
||||
|
||||
/** Creates a copy of another identifier. */
|
||||
Identifier (Identifier&& other) noexcept;
|
||||
|
||||
/** Creates a copy of another identifier. */
|
||||
Identifier& operator= (Identifier&& other) noexcept;
|
||||
|
||||
/** Destructor */
|
||||
~Identifier() noexcept;
|
||||
|
||||
/** Compares two identifiers. This is a very fast operation. */
|
||||
inline bool operator== (const Identifier& other) const noexcept { return name.getCharPointer() == other.name.getCharPointer(); }
|
||||
|
||||
/** Compares two identifiers. This is a very fast operation. */
|
||||
inline bool operator!= (const Identifier& other) const noexcept { return name.getCharPointer() != other.name.getCharPointer(); }
|
||||
|
||||
/** Compares the identifier with a string. */
|
||||
inline bool operator== (StringRef other) const noexcept { return name == other; }
|
||||
|
||||
/** Compares the identifier with a string. */
|
||||
inline bool operator!= (StringRef other) const noexcept { return name != other; }
|
||||
|
||||
/** Compares the identifier with a string. */
|
||||
inline bool operator< (StringRef other) const noexcept { return name < other; }
|
||||
|
||||
/** Compares the identifier with a string. */
|
||||
inline bool operator<= (StringRef other) const noexcept { return name <= other; }
|
||||
|
||||
/** Compares the identifier with a string. */
|
||||
inline bool operator> (StringRef other) const noexcept { return name > other; }
|
||||
|
||||
/** Compares the identifier with a string. */
|
||||
inline bool operator>= (StringRef other) const noexcept { return name >= other; }
|
||||
|
||||
/** Returns this identifier as a string. */
|
||||
const String& toString() const noexcept { return name; }
|
||||
|
||||
/** Returns this identifier's raw string pointer. */
|
||||
operator String::CharPointerType() const noexcept { return name.getCharPointer(); }
|
||||
|
||||
/** Returns this identifier's raw string pointer. */
|
||||
String::CharPointerType getCharPointer() const noexcept { return name.getCharPointer(); }
|
||||
|
||||
/** Returns this identifier as a StringRef. */
|
||||
operator StringRef() const noexcept { return name; }
|
||||
|
||||
/** Returns true if this Identifier is not null */
|
||||
bool isValid() const noexcept { return name.isNotEmpty(); }
|
||||
|
||||
/** Returns true if this Identifier is null */
|
||||
bool isNull() const noexcept { return name.isEmpty(); }
|
||||
|
||||
/** A null identifier. */
|
||||
static Identifier null;
|
||||
|
||||
/** Checks a given string for characters that might not be valid in an Identifier.
|
||||
Since Identifiers are used as a script variables and XML attributes, they should only contain
|
||||
alphanumeric characters, underscores, or the '-' and ':' characters.
|
||||
*/
|
||||
static bool isValidIdentifier (const String& possibleIdentifier) noexcept;
|
||||
|
||||
private:
|
||||
String name;
|
||||
};
|
||||
|
||||
} // namespace juce
|
206
modules/juce_core/text/juce_LocalisedStrings.cpp
Normal file
206
modules/juce_core/text/juce_LocalisedStrings.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
LocalisedStrings::LocalisedStrings (const String& fileContents, bool ignoreCase)
|
||||
{
|
||||
loadFromText (fileContents, ignoreCase);
|
||||
}
|
||||
|
||||
LocalisedStrings::LocalisedStrings (const File& fileToLoad, bool ignoreCase)
|
||||
{
|
||||
loadFromText (fileToLoad.loadFileAsString(), ignoreCase);
|
||||
}
|
||||
|
||||
LocalisedStrings::LocalisedStrings (const LocalisedStrings& other)
|
||||
: languageName (other.languageName), countryCodes (other.countryCodes),
|
||||
translations (other.translations), fallback (createCopyIfNotNull (other.fallback.get()))
|
||||
{
|
||||
}
|
||||
|
||||
LocalisedStrings& LocalisedStrings::operator= (const LocalisedStrings& other)
|
||||
{
|
||||
languageName = other.languageName;
|
||||
countryCodes = other.countryCodes;
|
||||
translations = other.translations;
|
||||
fallback.reset (createCopyIfNotNull (other.fallback.get()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
LocalisedStrings::~LocalisedStrings()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String LocalisedStrings::translate (const String& text) const
|
||||
{
|
||||
if (fallback != nullptr && ! translations.containsKey (text))
|
||||
return fallback->translate (text);
|
||||
|
||||
return translations.getValue (text, text);
|
||||
}
|
||||
|
||||
String LocalisedStrings::translate (const String& text, const String& resultIfNotFound) const
|
||||
{
|
||||
if (fallback != nullptr && ! translations.containsKey (text))
|
||||
return fallback->translate (text, resultIfNotFound);
|
||||
|
||||
return translations.getValue (text, resultIfNotFound);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
#if JUCE_CHECK_MEMORY_LEAKS
|
||||
// By using this object to force a LocalisedStrings object to be created
|
||||
// before the currentMappings object, we can force the static order-of-destruction to
|
||||
// delete the currentMappings object first, which avoids a bogus leak warning.
|
||||
// (Oddly, just creating a LocalisedStrings on the stack doesn't work in gcc, it
|
||||
// has to be created with 'new' for this to work..)
|
||||
struct LeakAvoidanceTrick
|
||||
{
|
||||
LeakAvoidanceTrick()
|
||||
{
|
||||
const std::unique_ptr<LocalisedStrings> dummy (new LocalisedStrings (String(), false));
|
||||
}
|
||||
};
|
||||
|
||||
LeakAvoidanceTrick leakAvoidanceTrick;
|
||||
#endif
|
||||
|
||||
SpinLock currentMappingsLock;
|
||||
std::unique_ptr<LocalisedStrings> currentMappings;
|
||||
|
||||
static int findCloseQuote (const String& text, int startPos)
|
||||
{
|
||||
juce_wchar lastChar = 0;
|
||||
auto t = text.getCharPointer() + startPos;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = t.getAndAdvance();
|
||||
|
||||
if (c == 0 || (c == '"' && lastChar != '\\'))
|
||||
break;
|
||||
|
||||
lastChar = c;
|
||||
++startPos;
|
||||
}
|
||||
|
||||
return startPos;
|
||||
}
|
||||
|
||||
static String unescapeString (const String& s)
|
||||
{
|
||||
return s.replace ("\\\"", "\"")
|
||||
.replace ("\\\'", "\'")
|
||||
.replace ("\\t", "\t")
|
||||
.replace ("\\r", "\r")
|
||||
.replace ("\\n", "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void LocalisedStrings::loadFromText (const String& fileContents, bool ignoreCase)
|
||||
{
|
||||
translations.setIgnoresCase (ignoreCase);
|
||||
|
||||
StringArray lines;
|
||||
lines.addLines (fileContents);
|
||||
|
||||
for (auto& l : lines)
|
||||
{
|
||||
auto line = l.trim();
|
||||
|
||||
if (line.startsWithChar ('"'))
|
||||
{
|
||||
auto closeQuote = findCloseQuote (line, 1);
|
||||
auto originalText = unescapeString (line.substring (1, closeQuote));
|
||||
|
||||
if (originalText.isNotEmpty())
|
||||
{
|
||||
auto openingQuote = findCloseQuote (line, closeQuote + 1);
|
||||
closeQuote = findCloseQuote (line, openingQuote + 1);
|
||||
auto newText = unescapeString (line.substring (openingQuote + 1, closeQuote));
|
||||
|
||||
if (newText.isNotEmpty())
|
||||
translations.set (originalText, newText);
|
||||
}
|
||||
}
|
||||
else if (line.startsWithIgnoreCase ("language:"))
|
||||
{
|
||||
languageName = line.substring (9).trim();
|
||||
}
|
||||
else if (line.startsWithIgnoreCase ("countries:"))
|
||||
{
|
||||
countryCodes.addTokens (line.substring (10).trim(), true);
|
||||
countryCodes.trim();
|
||||
countryCodes.removeEmptyStrings();
|
||||
}
|
||||
}
|
||||
|
||||
translations.minimiseStorageOverheads();
|
||||
}
|
||||
|
||||
void LocalisedStrings::addStrings (const LocalisedStrings& other)
|
||||
{
|
||||
jassert (languageName == other.languageName);
|
||||
jassert (countryCodes == other.countryCodes);
|
||||
|
||||
translations.addArray (other.translations);
|
||||
}
|
||||
|
||||
void LocalisedStrings::setFallback (LocalisedStrings* f)
|
||||
{
|
||||
fallback.reset (f);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations)
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (currentMappingsLock);
|
||||
currentMappings.reset (newTranslations);
|
||||
}
|
||||
|
||||
LocalisedStrings* LocalisedStrings::getCurrentMappings()
|
||||
{
|
||||
return currentMappings.get();
|
||||
}
|
||||
|
||||
String LocalisedStrings::translateWithCurrentMappings (const String& text) { return juce::translate (text); }
|
||||
String LocalisedStrings::translateWithCurrentMappings (const char* text) { return juce::translate (text); }
|
||||
|
||||
JUCE_API String translate (const String& text) { return juce::translate (text, text); }
|
||||
JUCE_API String translate (const char* text) { return juce::translate (String (text)); }
|
||||
JUCE_API String translate (CharPointer_UTF8 text) { return juce::translate (String (text)); }
|
||||
|
||||
JUCE_API String translate (const String& text, const String& resultIfNotFound)
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (currentMappingsLock);
|
||||
|
||||
if (auto* mappings = LocalisedStrings::getCurrentMappings())
|
||||
return mappings->translate (text, resultIfNotFound);
|
||||
|
||||
return resultIfNotFound;
|
||||
}
|
||||
|
||||
} // namespace juce
|
241
modules/juce_core/text/juce_LocalisedStrings.h
Normal file
241
modules/juce_core/text/juce_LocalisedStrings.h
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Used to convert strings to localised foreign-language versions.
|
||||
|
||||
This is basically a look-up table of strings and their translated equivalents.
|
||||
It can be loaded from a text file, so that you can supply a set of localised
|
||||
versions of strings that you use in your app.
|
||||
|
||||
To use it in your code, simply call the translate() method on each string that
|
||||
might have foreign versions, and if none is found, the method will just return
|
||||
the original string.
|
||||
|
||||
The translation file should start with some lines specifying a description of
|
||||
the language it contains, and also a list of ISO country codes where it might
|
||||
be appropriate to use the file. After that, each line of the file should contain
|
||||
a pair of quoted strings with an '=' sign.
|
||||
|
||||
E.g. for a french translation, the file might be:
|
||||
|
||||
@code
|
||||
language: French
|
||||
countries: fr be mc ch lu
|
||||
|
||||
"hello" = "bonjour"
|
||||
"goodbye" = "au revoir"
|
||||
@endcode
|
||||
|
||||
If the strings need to contain a quote character, they can use '\"' instead, and
|
||||
if the first non-whitespace character on a line isn't a quote, then it's ignored,
|
||||
(you can use this to add comments).
|
||||
|
||||
Note that this is a singleton class, so don't create or destroy the object directly.
|
||||
There's also a TRANS(text) macro defined to make it easy to use the this.
|
||||
|
||||
E.g. @code
|
||||
printSomething (TRANS("hello"));
|
||||
@endcode
|
||||
|
||||
This macro is used in the JUCE classes themselves, so your application has a chance to
|
||||
intercept and translate any internal JUCE text strings that might be shown. (You can easily
|
||||
get a list of all the messages by searching for the TRANS() macro in the JUCE source
|
||||
code).
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API LocalisedStrings
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a set of translations from the text of a translation file.
|
||||
|
||||
When you create one of these, you can call setCurrentMappings() to make it
|
||||
the set of mappings that the system's using.
|
||||
*/
|
||||
LocalisedStrings (const String& fileContents, bool ignoreCaseOfKeys);
|
||||
|
||||
/** Creates a set of translations from a file.
|
||||
|
||||
When you create one of these, you can call setCurrentMappings() to make it
|
||||
the set of mappings that the system's using.
|
||||
*/
|
||||
LocalisedStrings (const File& fileToLoad, bool ignoreCaseOfKeys);
|
||||
|
||||
LocalisedStrings (const LocalisedStrings&);
|
||||
LocalisedStrings& operator= (const LocalisedStrings&);
|
||||
|
||||
/** Destructor. */
|
||||
~LocalisedStrings();
|
||||
|
||||
//==============================================================================
|
||||
/** Selects the current set of mappings to be used by the system.
|
||||
|
||||
The object you pass in will be automatically deleted when no longer needed, so
|
||||
don't keep a pointer to it. You can also pass in nullptr to remove the current
|
||||
mappings.
|
||||
|
||||
See also the TRANS() macro, which uses the current set to do its translation.
|
||||
|
||||
@see translateWithCurrentMappings
|
||||
*/
|
||||
static void setCurrentMappings (LocalisedStrings* newTranslations);
|
||||
|
||||
/** Returns the currently selected set of mappings.
|
||||
|
||||
This is the object that was last passed to setCurrentMappings(). It may
|
||||
be nullptr if none has been created.
|
||||
*/
|
||||
static LocalisedStrings* getCurrentMappings();
|
||||
|
||||
/** Tries to translate a string using the currently selected set of mappings.
|
||||
|
||||
If no mapping has been set, or if the mapping doesn't contain a translation
|
||||
for the string, this will just return the original string.
|
||||
|
||||
See also the TRANS() macro, which uses this method to do its translation.
|
||||
|
||||
@see setCurrentMappings, getCurrentMappings
|
||||
*/
|
||||
static String translateWithCurrentMappings (const String& text);
|
||||
|
||||
/** Tries to translate a string using the currently selected set of mappings.
|
||||
|
||||
If no mapping has been set, or if the mapping doesn't contain a translation
|
||||
for the string, this will just return the original string.
|
||||
|
||||
See also the TRANS() macro, which uses this method to do its translation.
|
||||
|
||||
@see setCurrentMappings, getCurrentMappings
|
||||
*/
|
||||
static String translateWithCurrentMappings (const char* text);
|
||||
|
||||
//==============================================================================
|
||||
/** Attempts to look up a string and return its localised version.
|
||||
If the string isn't found in the list, the original string will be returned.
|
||||
*/
|
||||
String translate (const String& text) const;
|
||||
|
||||
/** Attempts to look up a string and return its localised version.
|
||||
If the string isn't found in the list, the resultIfNotFound string will be returned.
|
||||
*/
|
||||
String translate (const String& text, const String& resultIfNotFound) const;
|
||||
|
||||
/** Returns the name of the language specified in the translation file.
|
||||
|
||||
This is specified in the file using a line starting with "language:", e.g.
|
||||
@code
|
||||
language: german
|
||||
@endcode
|
||||
*/
|
||||
String getLanguageName() const { return languageName; }
|
||||
|
||||
/** Returns the list of suitable country codes listed in the translation file.
|
||||
|
||||
These is specified in the file using a line starting with "countries:", e.g.
|
||||
@code
|
||||
countries: fr be mc ch lu
|
||||
@endcode
|
||||
|
||||
The country codes are supposed to be 2-character ISO complient codes.
|
||||
*/
|
||||
const StringArray& getCountryCodes() const { return countryCodes; }
|
||||
|
||||
/** Provides access to the actual list of mappings. */
|
||||
const StringPairArray& getMappings() const { return translations; }
|
||||
|
||||
//==============================================================================
|
||||
/** Adds and merges another set of translations into this set.
|
||||
|
||||
Note that the language name and country codes of the new LocalisedStrings
|
||||
object must match that of this object - an assertion will be thrown if they
|
||||
don't match.
|
||||
|
||||
Any existing values will have their mappings overwritten by the new ones.
|
||||
*/
|
||||
void addStrings (const LocalisedStrings&);
|
||||
|
||||
/** Gives this object a set of strings to use as a fallback if a string isn't found.
|
||||
The object that is passed-in will be owned and deleted by this object
|
||||
when no longer needed. It can be nullptr to clear the existing fallback object.
|
||||
*/
|
||||
void setFallback (LocalisedStrings* fallbackStrings);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String languageName;
|
||||
StringArray countryCodes;
|
||||
StringPairArray translations;
|
||||
std::unique_ptr<LocalisedStrings> fallback;
|
||||
friend struct ContainerDeletePolicy<LocalisedStrings>;
|
||||
|
||||
void loadFromText (const String&, bool ignoreCase);
|
||||
|
||||
JUCE_LEAK_DETECTOR (LocalisedStrings)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#ifndef TRANS
|
||||
/** Uses the LocalisedStrings class to translate the given string literal.
|
||||
This macro is provided for backwards-compatibility, and just calls the translate()
|
||||
function. In new code, it's recommended that you just call translate() directly
|
||||
instead, and avoid using macros.
|
||||
@see translate(), LocalisedStrings
|
||||
*/
|
||||
#define TRANS(stringLiteral) juce::translate (stringLiteral)
|
||||
#endif
|
||||
|
||||
/** A dummy version of the TRANS macro, used to indicate a string literal that should be
|
||||
added to the translation file by source-code scanner tools.
|
||||
|
||||
Wrapping a string literal in this macro has no effect, but by using it around strings
|
||||
that your app needs to translate at a later stage, it lets automatic code-scanning tools
|
||||
find this string and add it to the list of strings that need translation.
|
||||
*/
|
||||
#define NEEDS_TRANS(stringLiteral) (stringLiteral)
|
||||
|
||||
/** Uses the LocalisedStrings class to translate the given string literal.
|
||||
@see LocalisedStrings
|
||||
*/
|
||||
JUCE_API String translate (const String& stringLiteral);
|
||||
|
||||
/** Uses the LocalisedStrings class to translate the given string literal.
|
||||
@see LocalisedStrings
|
||||
*/
|
||||
JUCE_API String translate (const char* stringLiteral);
|
||||
|
||||
/** Uses the LocalisedStrings class to translate the given string literal.
|
||||
@see LocalisedStrings
|
||||
*/
|
||||
JUCE_API String translate (CharPointer_UTF8 stringLiteral);
|
||||
|
||||
/** Uses the LocalisedStrings class to translate the given string literal.
|
||||
@see LocalisedStrings
|
||||
*/
|
||||
JUCE_API String translate (const String& stringLiteral, const String& resultIfNotFound);
|
||||
|
||||
} // namespace juce
|
82
modules/juce_core/text/juce_NewLine.h
Normal file
82
modules/juce_core/text/juce_NewLine.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** This class is used for represent a new-line character sequence.
|
||||
|
||||
To write a new-line to a stream, you can use the predefined 'newLine' variable, e.g.
|
||||
@code
|
||||
myOutputStream << "Hello World" << newLine << newLine;
|
||||
@endcode
|
||||
|
||||
The exact character sequence that will be used for the new-line can be set and
|
||||
retrieved with OutputStream::setNewLineString() and OutputStream::getNewLineString().
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API NewLine
|
||||
{
|
||||
public:
|
||||
/** Returns the default new-line sequence that the library uses.
|
||||
@see OutputStream::setNewLineString()
|
||||
*/
|
||||
static const char* getDefault() noexcept { return "\r\n"; }
|
||||
|
||||
/** Returns the default new-line sequence that the library uses.
|
||||
@see getDefault()
|
||||
*/
|
||||
operator String() const { return getDefault(); }
|
||||
|
||||
/** Returns the default new-line sequence that the library uses.
|
||||
@see OutputStream::setNewLineString()
|
||||
*/
|
||||
operator StringRef() const noexcept { return getDefault(); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A predefined object representing a new-line, which can be written to a string or stream.
|
||||
|
||||
To write a new-line to a stream, you can use the predefined 'newLine' variable like this:
|
||||
@code
|
||||
myOutputStream << "Hello World" << newLine << newLine;
|
||||
@endcode
|
||||
*/
|
||||
extern NewLine newLine;
|
||||
|
||||
//==============================================================================
|
||||
/** Writes a new-line sequence to a string.
|
||||
You can use the predefined object 'newLine' to invoke this, e.g.
|
||||
@code
|
||||
myString << "Hello World" << newLine << newLine;
|
||||
@endcode
|
||||
*/
|
||||
inline String& operator<< (String& string1, const NewLine&) { return string1 += NewLine::getDefault(); }
|
||||
inline String& operator+= (String& s1, const NewLine&) { return s1 += NewLine::getDefault(); }
|
||||
|
||||
inline String operator+ (const NewLine&, const NewLine&) { return String (NewLine::getDefault()) + NewLine::getDefault(); }
|
||||
inline String operator+ (String s1, const NewLine&) { return s1 += NewLine::getDefault(); }
|
||||
inline String operator+ (const NewLine&, const char* s2) { return String (NewLine::getDefault()) + s2; }
|
||||
|
||||
} // namespace juce
|
2691
modules/juce_core/text/juce_String.cpp
Normal file
2691
modules/juce_core/text/juce_String.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1430
modules/juce_core/text/juce_String.h
Normal file
1430
modules/juce_core/text/juce_String.h
Normal file
File diff suppressed because it is too large
Load Diff
465
modules/juce_core/text/juce_StringArray.cpp
Normal file
465
modules/juce_core/text/juce_StringArray.cpp
Normal file
@ -0,0 +1,465 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StringArray::StringArray() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
StringArray::StringArray (const StringArray& other)
|
||||
: strings (other.strings)
|
||||
{
|
||||
}
|
||||
|
||||
StringArray::StringArray (StringArray&& other) noexcept
|
||||
: strings (static_cast<Array<String>&&> (other.strings))
|
||||
{
|
||||
}
|
||||
|
||||
StringArray::StringArray (const String& firstValue)
|
||||
{
|
||||
strings.add (firstValue);
|
||||
}
|
||||
|
||||
StringArray::StringArray (const String* initialStrings, int numberOfStrings)
|
||||
{
|
||||
strings.addArray (initialStrings, numberOfStrings);
|
||||
}
|
||||
|
||||
StringArray::StringArray (const char* const* initialStrings)
|
||||
{
|
||||
strings.addNullTerminatedArray (initialStrings);
|
||||
}
|
||||
|
||||
StringArray::StringArray (const char* const* initialStrings, int numberOfStrings)
|
||||
{
|
||||
strings.addArray (initialStrings, numberOfStrings);
|
||||
}
|
||||
|
||||
StringArray::StringArray (const wchar_t* const* initialStrings)
|
||||
{
|
||||
strings.addNullTerminatedArray (initialStrings);
|
||||
}
|
||||
|
||||
StringArray::StringArray (const wchar_t* const* initialStrings, int numberOfStrings)
|
||||
{
|
||||
strings.addArray (initialStrings, numberOfStrings);
|
||||
}
|
||||
|
||||
StringArray::StringArray (const std::initializer_list<const char*>& stringList)
|
||||
{
|
||||
strings.addArray (stringList);
|
||||
}
|
||||
|
||||
StringArray& StringArray::operator= (const StringArray& other)
|
||||
{
|
||||
strings = other.strings;
|
||||
return *this;
|
||||
}
|
||||
|
||||
StringArray& StringArray::operator= (StringArray&& other) noexcept
|
||||
{
|
||||
strings = static_cast<Array<String>&&> (other.strings);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StringArray::~StringArray()
|
||||
{
|
||||
}
|
||||
|
||||
bool StringArray::operator== (const StringArray& other) const noexcept
|
||||
{
|
||||
return strings == other.strings;
|
||||
}
|
||||
|
||||
bool StringArray::operator!= (const StringArray& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
void StringArray::swapWith (StringArray& other) noexcept
|
||||
{
|
||||
strings.swapWith (other.strings);
|
||||
}
|
||||
|
||||
void StringArray::clear()
|
||||
{
|
||||
strings.clear();
|
||||
}
|
||||
|
||||
void StringArray::clearQuick()
|
||||
{
|
||||
strings.clearQuick();
|
||||
}
|
||||
|
||||
const String& StringArray::operator[] (int index) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (index, strings.size()))
|
||||
return strings.getReference (index);
|
||||
|
||||
static String empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
String& StringArray::getReference (int index) noexcept
|
||||
{
|
||||
return strings.getReference (index);
|
||||
}
|
||||
|
||||
void StringArray::add (const String& newString)
|
||||
{
|
||||
strings.add (newString);
|
||||
}
|
||||
|
||||
void StringArray::add (String&& stringToAdd)
|
||||
{
|
||||
strings.add (static_cast<String&&> (stringToAdd));
|
||||
}
|
||||
|
||||
void StringArray::insert (int index, const String& newString)
|
||||
{
|
||||
strings.insert (index, newString);
|
||||
}
|
||||
|
||||
bool StringArray::addIfNotAlreadyThere (const String& newString, bool ignoreCase)
|
||||
{
|
||||
if (contains (newString, ignoreCase))
|
||||
return false;
|
||||
|
||||
add (newString);
|
||||
return true;
|
||||
}
|
||||
|
||||
void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd)
|
||||
{
|
||||
if (startIndex < 0)
|
||||
{
|
||||
jassertfalse;
|
||||
startIndex = 0;
|
||||
}
|
||||
|
||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size())
|
||||
numElementsToAdd = otherArray.size() - startIndex;
|
||||
|
||||
while (--numElementsToAdd >= 0)
|
||||
strings.add (otherArray.strings.getReference (startIndex++));
|
||||
}
|
||||
|
||||
void StringArray::mergeArray (const StringArray& otherArray, bool ignoreCase)
|
||||
{
|
||||
for (auto& s : otherArray)
|
||||
addIfNotAlreadyThere (s, ignoreCase);
|
||||
}
|
||||
|
||||
void StringArray::set (int index, const String& newString)
|
||||
{
|
||||
strings.set (index, newString);
|
||||
}
|
||||
|
||||
bool StringArray::contains (StringRef stringToLookFor, bool ignoreCase) const
|
||||
{
|
||||
return indexOf (stringToLookFor, ignoreCase) >= 0;
|
||||
}
|
||||
|
||||
int StringArray::indexOf (StringRef stringToLookFor, bool ignoreCase, int i) const
|
||||
{
|
||||
if (i < 0)
|
||||
i = 0;
|
||||
|
||||
auto numElements = size();
|
||||
|
||||
if (ignoreCase)
|
||||
{
|
||||
for (; i < numElements; ++i)
|
||||
if (strings.getReference(i).equalsIgnoreCase (stringToLookFor))
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; i < numElements; ++i)
|
||||
if (stringToLookFor == strings.getReference (i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void StringArray::move (int currentIndex, int newIndex) noexcept
|
||||
{
|
||||
strings.move (currentIndex, newIndex);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StringArray::remove (int index)
|
||||
{
|
||||
strings.remove (index);
|
||||
}
|
||||
|
||||
void StringArray::removeString (StringRef stringToRemove, bool ignoreCase)
|
||||
{
|
||||
if (ignoreCase)
|
||||
{
|
||||
for (int i = size(); --i >= 0;)
|
||||
if (strings.getReference(i).equalsIgnoreCase (stringToRemove))
|
||||
strings.remove (i);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = size(); --i >= 0;)
|
||||
if (stringToRemove == strings.getReference (i))
|
||||
strings.remove (i);
|
||||
}
|
||||
}
|
||||
|
||||
void StringArray::removeRange (int startIndex, int numberToRemove)
|
||||
{
|
||||
strings.removeRange (startIndex, numberToRemove);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StringArray::removeEmptyStrings (bool removeWhitespaceStrings)
|
||||
{
|
||||
if (removeWhitespaceStrings)
|
||||
{
|
||||
for (int i = size(); --i >= 0;)
|
||||
if (! strings.getReference(i).containsNonWhitespaceChars())
|
||||
strings.remove (i);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = size(); --i >= 0;)
|
||||
if (strings.getReference(i).isEmpty())
|
||||
strings.remove (i);
|
||||
}
|
||||
}
|
||||
|
||||
void StringArray::trim()
|
||||
{
|
||||
for (auto& s : strings)
|
||||
s = s.trim();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StringArray::sort (bool ignoreCase)
|
||||
{
|
||||
if (ignoreCase)
|
||||
std::sort (strings.begin(), strings.end(),
|
||||
[] (const String& a, const String& b) { return a.compareIgnoreCase (b) < 0; });
|
||||
else
|
||||
std::sort (strings.begin(), strings.end());
|
||||
}
|
||||
|
||||
void StringArray::sortNatural()
|
||||
{
|
||||
std::sort (strings.begin(), strings.end(),
|
||||
[] (const String& a, const String& b) { return a.compareNatural (b) < 0; });
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String StringArray::joinIntoString (StringRef separator, int start, int numberToJoin) const
|
||||
{
|
||||
auto last = (numberToJoin < 0) ? size()
|
||||
: jmin (size(), start + numberToJoin);
|
||||
|
||||
if (start < 0)
|
||||
start = 0;
|
||||
|
||||
if (start >= last)
|
||||
return {};
|
||||
|
||||
if (start == last - 1)
|
||||
return strings.getReference (start);
|
||||
|
||||
auto separatorBytes = separator.text.sizeInBytes() - sizeof (String::CharPointerType::CharType);
|
||||
auto bytesNeeded = separatorBytes * (size_t) (last - start - 1);
|
||||
|
||||
for (int i = start; i < last; ++i)
|
||||
bytesNeeded += strings.getReference(i).getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType);
|
||||
|
||||
String result;
|
||||
result.preallocateBytes (bytesNeeded);
|
||||
|
||||
auto dest = result.getCharPointer();
|
||||
|
||||
while (start < last)
|
||||
{
|
||||
auto& s = strings.getReference (start);
|
||||
|
||||
if (! s.isEmpty())
|
||||
dest.writeAll (s.getCharPointer());
|
||||
|
||||
if (++start < last && separatorBytes > 0)
|
||||
dest.writeAll (separator.text);
|
||||
}
|
||||
|
||||
dest.writeNull();
|
||||
return result;
|
||||
}
|
||||
|
||||
int StringArray::addTokens (StringRef text, const bool preserveQuotedStrings)
|
||||
{
|
||||
return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : "");
|
||||
}
|
||||
|
||||
int StringArray::addTokens (StringRef text, StringRef breakCharacters, StringRef quoteCharacters)
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
if (text.isNotEmpty())
|
||||
{
|
||||
for (auto t = text.text;;)
|
||||
{
|
||||
auto tokenEnd = CharacterFunctions::findEndOfToken (t,
|
||||
breakCharacters.text,
|
||||
quoteCharacters.text);
|
||||
strings.add (String (t, tokenEnd));
|
||||
++num;
|
||||
|
||||
if (tokenEnd.isEmpty())
|
||||
break;
|
||||
|
||||
t = ++tokenEnd;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
int StringArray::addLines (StringRef sourceText)
|
||||
{
|
||||
int numLines = 0;
|
||||
auto text = sourceText.text;
|
||||
bool finished = text.isEmpty();
|
||||
|
||||
while (! finished)
|
||||
{
|
||||
for (auto startOfLine = text;;)
|
||||
{
|
||||
auto endOfLine = text;
|
||||
|
||||
switch (text.getAndAdvance())
|
||||
{
|
||||
case 0: finished = true; break;
|
||||
case '\n': break;
|
||||
case '\r': if (*text == '\n') ++text; break;
|
||||
default: continue;
|
||||
}
|
||||
|
||||
strings.add (String (startOfLine, endOfLine));
|
||||
++numLines;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return numLines;
|
||||
}
|
||||
|
||||
StringArray StringArray::fromTokens (StringRef stringToTokenise, bool preserveQuotedStrings)
|
||||
{
|
||||
StringArray s;
|
||||
s.addTokens (stringToTokenise, preserveQuotedStrings);
|
||||
return s;
|
||||
}
|
||||
|
||||
StringArray StringArray::fromTokens (StringRef stringToTokenise,
|
||||
StringRef breakCharacters,
|
||||
StringRef quoteCharacters)
|
||||
{
|
||||
StringArray s;
|
||||
s.addTokens (stringToTokenise, breakCharacters, quoteCharacters);
|
||||
return s;
|
||||
}
|
||||
|
||||
StringArray StringArray::fromLines (StringRef stringToBreakUp)
|
||||
{
|
||||
StringArray s;
|
||||
s.addLines (stringToBreakUp);
|
||||
return s;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StringArray::removeDuplicates (bool ignoreCase)
|
||||
{
|
||||
for (int i = 0; i < size() - 1; ++i)
|
||||
{
|
||||
auto s = strings.getReference(i);
|
||||
|
||||
for (int nextIndex = i + 1;;)
|
||||
{
|
||||
nextIndex = indexOf (s, ignoreCase, nextIndex);
|
||||
|
||||
if (nextIndex < 0)
|
||||
break;
|
||||
|
||||
strings.remove (nextIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StringArray::appendNumbersToDuplicates (bool ignoreCase,
|
||||
bool appendNumberToFirstInstance,
|
||||
CharPointer_UTF8 preNumberString,
|
||||
CharPointer_UTF8 postNumberString)
|
||||
{
|
||||
if (preNumberString.getAddress() == nullptr)
|
||||
preNumberString = CharPointer_UTF8 (" (");
|
||||
|
||||
if (postNumberString.getAddress() == nullptr)
|
||||
postNumberString = CharPointer_UTF8 (")");
|
||||
|
||||
for (int i = 0; i < size() - 1; ++i)
|
||||
{
|
||||
auto& s = strings.getReference(i);
|
||||
auto nextIndex = indexOf (s, ignoreCase, i + 1);
|
||||
|
||||
if (nextIndex >= 0)
|
||||
{
|
||||
auto original = s;
|
||||
int number = 0;
|
||||
|
||||
if (appendNumberToFirstInstance)
|
||||
s = original + String (preNumberString) + String (++number) + String (postNumberString);
|
||||
else
|
||||
++number;
|
||||
|
||||
while (nextIndex >= 0)
|
||||
{
|
||||
set (nextIndex, (*this)[nextIndex] + String (preNumberString) + String (++number) + String (postNumberString));
|
||||
nextIndex = indexOf (original, ignoreCase, nextIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StringArray::ensureStorageAllocated (int minNumElements)
|
||||
{
|
||||
strings.ensureStorageAllocated (minNumElements);
|
||||
}
|
||||
|
||||
void StringArray::minimiseStorageOverheads()
|
||||
{
|
||||
strings.minimiseStorageOverheads();
|
||||
}
|
||||
|
||||
} // namespace juce
|
432
modules/juce_core/text/juce_StringArray.h
Normal file
432
modules/juce_core/text/juce_StringArray.h
Normal file
@ -0,0 +1,432 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A special array for holding a list of strings.
|
||||
|
||||
@see String, StringPairArray
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API StringArray
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty string array */
|
||||
StringArray() noexcept;
|
||||
|
||||
/** Creates a copy of another string array */
|
||||
StringArray (const StringArray&);
|
||||
|
||||
/** Move constructor */
|
||||
StringArray (StringArray&&) noexcept;
|
||||
|
||||
/** Creates an array containing a single string. */
|
||||
StringArray (const String& firstValue);
|
||||
|
||||
/** Creates an array containing a list of strings. */
|
||||
template <typename... OtherElements>
|
||||
StringArray (StringRef firstValue, OtherElements... otherValues) : strings (firstValue, otherValues...) {}
|
||||
|
||||
/** Creates an array containing a list of strings. */
|
||||
StringArray (const std::initializer_list<const char*>& strings);
|
||||
|
||||
/** Creates an array from a raw array of strings.
|
||||
@param strings an array of strings to add
|
||||
@param numberOfStrings how many items there are in the array
|
||||
*/
|
||||
StringArray (const String* strings, int numberOfStrings);
|
||||
|
||||
/** Creates a copy of an array of string literals.
|
||||
@param strings an array of strings to add. Null pointers in the array will be
|
||||
treated as empty strings
|
||||
@param numberOfStrings how many items there are in the array
|
||||
*/
|
||||
StringArray (const char* const* strings, int numberOfStrings);
|
||||
|
||||
/** Creates a copy of a null-terminated array of string literals.
|
||||
|
||||
Each item from the array passed-in is added, until it encounters a null pointer,
|
||||
at which point it stops.
|
||||
*/
|
||||
explicit StringArray (const char* const* strings);
|
||||
|
||||
/** Creates a copy of a null-terminated array of string literals.
|
||||
Each item from the array passed-in is added, until it encounters a null pointer,
|
||||
at which point it stops.
|
||||
*/
|
||||
explicit StringArray (const wchar_t* const* strings);
|
||||
|
||||
/** Creates a copy of an array of string literals.
|
||||
@param strings an array of strings to add. Null pointers in the array will be
|
||||
treated as empty strings
|
||||
@param numberOfStrings how many items there are in the array
|
||||
*/
|
||||
StringArray (const wchar_t* const* strings, int numberOfStrings);
|
||||
|
||||
/** Destructor. */
|
||||
~StringArray();
|
||||
|
||||
/** Copies the contents of another string array into this one */
|
||||
StringArray& operator= (const StringArray&);
|
||||
|
||||
/** Move assignment operator */
|
||||
StringArray& operator= (StringArray&&) noexcept;
|
||||
|
||||
/** Swaps the contents of this and another StringArray. */
|
||||
void swapWith (StringArray&) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Compares two arrays.
|
||||
Comparisons are case-sensitive.
|
||||
@returns true only if the other array contains exactly the same strings in the same order
|
||||
*/
|
||||
bool operator== (const StringArray&) const noexcept;
|
||||
|
||||
/** Compares two arrays.
|
||||
Comparisons are case-sensitive.
|
||||
@returns false if the other array contains exactly the same strings in the same order
|
||||
*/
|
||||
bool operator!= (const StringArray&) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of strings in the array */
|
||||
inline int size() const noexcept { return strings.size(); }
|
||||
|
||||
/** Returns true if the array is empty, false otherwise. */
|
||||
inline bool isEmpty() const noexcept { return size() == 0; }
|
||||
|
||||
/** Returns one of the strings from the array.
|
||||
|
||||
If the index is out-of-range, an empty string is returned.
|
||||
|
||||
Obviously the reference returned shouldn't be stored for later use, as the
|
||||
string it refers to may disappear when the array changes.
|
||||
*/
|
||||
const String& operator[] (int index) const noexcept;
|
||||
|
||||
/** Returns a reference to one of the strings in the array.
|
||||
This lets you modify a string in-place in the array, but you must be sure that
|
||||
the index is in-range.
|
||||
*/
|
||||
String& getReference (int index) noexcept;
|
||||
|
||||
/** Returns a pointer to the first String in the array.
|
||||
This method is provided for compatibility with standard C++ iteration mechanisms.
|
||||
*/
|
||||
inline String* begin() const noexcept { return strings.begin(); }
|
||||
|
||||
/** Returns a pointer to the String which follows the last element in the array.
|
||||
This method is provided for compatibility with standard C++ iteration mechanisms.
|
||||
*/
|
||||
inline String* end() const noexcept { return strings.end(); }
|
||||
|
||||
/** Searches for a string in the array.
|
||||
|
||||
The comparison will be case-insensitive if the ignoreCase parameter is true.
|
||||
|
||||
@returns true if the string is found inside the array
|
||||
*/
|
||||
bool contains (StringRef stringToLookFor,
|
||||
bool ignoreCase = false) const;
|
||||
|
||||
/** Searches for a string in the array.
|
||||
|
||||
The comparison will be case-insensitive if the ignoreCase parameter is true.
|
||||
|
||||
@param stringToLookFor the string to try to find
|
||||
@param ignoreCase whether the comparison should be case-insensitive
|
||||
@param startIndex the first index to start searching from
|
||||
@returns the index of the first occurrence of the string in this array,
|
||||
or -1 if it isn't found.
|
||||
*/
|
||||
int indexOf (StringRef stringToLookFor,
|
||||
bool ignoreCase = false,
|
||||
int startIndex = 0) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Appends a string at the end of the array. */
|
||||
void add (const String& stringToAdd);
|
||||
|
||||
/** Appends a string at the end of the array. */
|
||||
void add (String&& stringToAdd);
|
||||
|
||||
/** Inserts a string into the array.
|
||||
|
||||
This will insert a string into the array at the given index, moving
|
||||
up the other elements to make room for it.
|
||||
If the index is less than zero or greater than the size of the array,
|
||||
the new string will be added to the end of the array.
|
||||
*/
|
||||
void insert (int index, const String& stringToAdd);
|
||||
|
||||
/** Adds a string to the array as long as it's not already in there.
|
||||
The search can optionally be case-insensitive.
|
||||
|
||||
@return true if the string has been added, false otherwise.
|
||||
*/
|
||||
bool addIfNotAlreadyThere (const String& stringToAdd, bool ignoreCase = false);
|
||||
|
||||
/** Replaces one of the strings in the array with another one.
|
||||
|
||||
If the index is higher than the array's size, the new string will be
|
||||
added to the end of the array; if it's less than zero nothing happens.
|
||||
*/
|
||||
void set (int index, const String& newString);
|
||||
|
||||
/** Appends some strings from another array to the end of this one.
|
||||
|
||||
@param other the array to add
|
||||
@param startIndex the first element of the other array to add
|
||||
@param numElementsToAdd the maximum number of elements to add (if this is
|
||||
less than zero, they are all added)
|
||||
*/
|
||||
void addArray (const StringArray& other,
|
||||
int startIndex = 0,
|
||||
int numElementsToAdd = -1);
|
||||
|
||||
/** Merges the strings from another array into this one.
|
||||
This will not add a string that already exists.
|
||||
|
||||
@param other the array to add
|
||||
@param ignoreCase ignore case when merging
|
||||
*/
|
||||
void mergeArray (const StringArray& other,
|
||||
bool ignoreCase = false);
|
||||
|
||||
/** Breaks up a string into tokens and adds them to this array.
|
||||
|
||||
This will tokenise the given string using whitespace characters as the
|
||||
token delimiters, and will add these tokens to the end of the array.
|
||||
@returns the number of tokens added
|
||||
@see fromTokens
|
||||
*/
|
||||
int addTokens (StringRef stringToTokenise, bool preserveQuotedStrings);
|
||||
|
||||
/** Breaks up a string into tokens and adds them to this array.
|
||||
|
||||
This will tokenise the given string (using the string passed in to define the
|
||||
token delimiters), and will add these tokens to the end of the array.
|
||||
|
||||
@param stringToTokenise the string to tokenise
|
||||
@param breakCharacters a string of characters, any of which will be considered
|
||||
to be a token delimiter.
|
||||
@param quoteCharacters if this string isn't empty, it defines a set of characters
|
||||
which are treated as quotes. Any text occurring
|
||||
between quotes is not broken up into tokens.
|
||||
@returns the number of tokens added
|
||||
@see fromTokens
|
||||
*/
|
||||
int addTokens (StringRef stringToTokenise,
|
||||
StringRef breakCharacters,
|
||||
StringRef quoteCharacters);
|
||||
|
||||
/** Breaks up a string into lines and adds them to this array.
|
||||
|
||||
This breaks a string down into lines separated by \\n or \\r\\n, and adds each line
|
||||
to the array. Line-break characters are omitted from the strings that are added to
|
||||
the array.
|
||||
*/
|
||||
int addLines (StringRef stringToBreakUp);
|
||||
|
||||
/** Returns an array containing the tokens in a given string.
|
||||
|
||||
This will tokenise the given string using whitespace characters as the
|
||||
token delimiters, and return the parsed tokens as an array.
|
||||
@see addTokens
|
||||
*/
|
||||
static StringArray fromTokens (StringRef stringToTokenise,
|
||||
bool preserveQuotedStrings);
|
||||
|
||||
/** Returns an array containing the tokens in a given string.
|
||||
|
||||
This will tokenise the given string using the breakCharacters string to define
|
||||
the token delimiters, and will return the parsed tokens as an array.
|
||||
|
||||
@param stringToTokenise the string to tokenise
|
||||
@param breakCharacters a string of characters, any of which will be considered
|
||||
to be a token delimiter.
|
||||
@param quoteCharacters if this string isn't empty, it defines a set of characters
|
||||
which are treated as quotes. Any text occurring
|
||||
between quotes is not broken up into tokens.
|
||||
@see addTokens
|
||||
*/
|
||||
static StringArray fromTokens (StringRef stringToTokenise,
|
||||
StringRef breakCharacters,
|
||||
StringRef quoteCharacters);
|
||||
|
||||
/** Returns an array containing the lines in a given string.
|
||||
|
||||
This breaks a string down into lines separated by \\n or \\r\\n, and returns an
|
||||
array containing these lines. Line-break characters are omitted from the strings that
|
||||
are added to the array.
|
||||
*/
|
||||
static StringArray fromLines (StringRef stringToBreakUp);
|
||||
|
||||
//==============================================================================
|
||||
/** Removes all elements from the array. */
|
||||
void clear();
|
||||
|
||||
/** Removes all elements from the array without freeing the array's allocated storage.
|
||||
@see clear
|
||||
*/
|
||||
void clearQuick();
|
||||
|
||||
/** Removes a string from the array.
|
||||
If the index is out-of-range, no action will be taken.
|
||||
*/
|
||||
void remove (int index);
|
||||
|
||||
/** Finds a string in the array and removes it.
|
||||
This will remove all occurrences of the given string from the array.
|
||||
The comparison may be case-insensitive depending on the ignoreCase parameter.
|
||||
*/
|
||||
void removeString (StringRef stringToRemove,
|
||||
bool ignoreCase = false);
|
||||
|
||||
/** Removes a range of elements from the array.
|
||||
|
||||
This will remove a set of elements, starting from the given index,
|
||||
and move subsequent elements down to close the gap.
|
||||
|
||||
If the range extends beyond the bounds of the array, it will
|
||||
be safely clipped to the size of the array.
|
||||
|
||||
@param startIndex the index of the first element to remove
|
||||
@param numberToRemove how many elements should be removed
|
||||
*/
|
||||
void removeRange (int startIndex, int numberToRemove);
|
||||
|
||||
/** Removes any duplicated elements from the array.
|
||||
|
||||
If any string appears in the array more than once, only the first occurrence of
|
||||
it will be retained.
|
||||
|
||||
@param ignoreCase whether to use a case-insensitive comparison
|
||||
*/
|
||||
void removeDuplicates (bool ignoreCase);
|
||||
|
||||
/** Removes empty strings from the array.
|
||||
@param removeWhitespaceStrings if true, strings that only contain whitespace
|
||||
characters will also be removed
|
||||
*/
|
||||
void removeEmptyStrings (bool removeWhitespaceStrings = true);
|
||||
|
||||
/** Moves one of the strings to a different position.
|
||||
|
||||
This will move the string to a specified index, shuffling along
|
||||
any intervening elements as required.
|
||||
|
||||
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling
|
||||
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }.
|
||||
|
||||
@param currentIndex the index of the value to be moved. If this isn't a
|
||||
valid index, then nothing will be done
|
||||
@param newIndex the index at which you'd like this value to end up. If this
|
||||
is less than zero, the value will be moved to the end
|
||||
of the array
|
||||
*/
|
||||
void move (int currentIndex, int newIndex) noexcept;
|
||||
|
||||
/** Deletes any whitespace characters from the starts and ends of all the strings. */
|
||||
void trim();
|
||||
|
||||
/** Adds numbers to the strings in the array, to make each string unique.
|
||||
|
||||
This will add numbers to the ends of groups of similar strings.
|
||||
e.g. if there are two "moose" strings, they will become "moose (1)" and "moose (2)"
|
||||
|
||||
@param ignoreCaseWhenComparing whether the comparison used is case-insensitive
|
||||
@param appendNumberToFirstInstance whether the first of a group of similar strings
|
||||
also has a number appended to it.
|
||||
@param preNumberString when adding a number, this string is added before the number.
|
||||
If you pass nullptr, a default string will be used, which adds
|
||||
brackets around the number.
|
||||
@param postNumberString this string is appended after any numbers that are added.
|
||||
If you pass nullptr, a default string will be used, which adds
|
||||
brackets around the number.
|
||||
*/
|
||||
void appendNumbersToDuplicates (bool ignoreCaseWhenComparing,
|
||||
bool appendNumberToFirstInstance,
|
||||
CharPointer_UTF8 preNumberString = CharPointer_UTF8 (nullptr),
|
||||
CharPointer_UTF8 postNumberString = CharPointer_UTF8 (nullptr));
|
||||
|
||||
//==============================================================================
|
||||
/** Joins the strings in the array together into one string.
|
||||
|
||||
This will join a range of elements from the array into a string, separating
|
||||
them with a given string.
|
||||
|
||||
e.g. joinIntoString (",") will turn an array of "a" "b" and "c" into "a,b,c".
|
||||
|
||||
@param separatorString the string to insert between all the strings
|
||||
@param startIndex the first element to join
|
||||
@param numberOfElements how many elements to join together. If this is less
|
||||
than zero, all available elements will be used.
|
||||
*/
|
||||
String joinIntoString (StringRef separatorString,
|
||||
int startIndex = 0,
|
||||
int numberOfElements = -1) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Sorts the array into alphabetical order.
|
||||
@param ignoreCase if true, the comparisons used will be case-sensitive.
|
||||
*/
|
||||
void sort (bool ignoreCase);
|
||||
|
||||
/** Sorts the array using extra language-aware rules to do a better job of comparing
|
||||
words containing spaces and numbers.
|
||||
@see String::compareNatural()
|
||||
*/
|
||||
void sortNatural();
|
||||
|
||||
//==============================================================================
|
||||
/** Increases the array's internal storage to hold a minimum number of elements.
|
||||
|
||||
Calling this before adding a large known number of elements means that
|
||||
the array won't have to keep dynamically resizing itself as the elements
|
||||
are added, and it'll therefore be more efficient.
|
||||
*/
|
||||
void ensureStorageAllocated (int minNumElements);
|
||||
|
||||
/** Reduces the amount of storage being used by the array.
|
||||
|
||||
Arrays typically allocate slightly more storage than they need, and after
|
||||
removing elements, they may have quite a lot of unused space allocated.
|
||||
This method will reduce the amount of allocated storage to a minimum.
|
||||
*/
|
||||
void minimiseStorageOverheads();
|
||||
|
||||
/** This is the array holding the actual strings. This is public to allow direct access
|
||||
to array methods that may not already be provided by the StringArray class.
|
||||
*/
|
||||
Array<String> strings;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (StringArray)
|
||||
};
|
||||
|
||||
} // namespace juce
|
169
modules/juce_core/text/juce_StringPairArray.cpp
Normal file
169
modules/juce_core/text/juce_StringPairArray.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StringPairArray::StringPairArray (bool shouldIgnoreCase) : ignoreCase (shouldIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
StringPairArray::StringPairArray (const StringPairArray& other)
|
||||
: keys (other.keys),
|
||||
values (other.values),
|
||||
ignoreCase (other.ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
StringPairArray::~StringPairArray()
|
||||
{
|
||||
}
|
||||
|
||||
StringPairArray& StringPairArray::operator= (const StringPairArray& other)
|
||||
{
|
||||
keys = other.keys;
|
||||
values = other.values;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool StringPairArray::operator== (const StringPairArray& other) const
|
||||
{
|
||||
auto num = size();
|
||||
|
||||
if (num != other.size())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < num; ++i)
|
||||
{
|
||||
if (keys[i] == other.keys[i]) // optimise for the case where the keys are in the same order
|
||||
{
|
||||
if (values[i] != other.values[i])
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we encounter keys that are in a different order, search remaining items by brute force..
|
||||
for (int j = i; j < num; ++j)
|
||||
{
|
||||
auto otherIndex = other.keys.indexOf (keys[j], other.ignoreCase);
|
||||
|
||||
if (otherIndex < 0 || values[j] != other.values[otherIndex])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StringPairArray::operator!= (const StringPairArray& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
const String& StringPairArray::operator[] (StringRef key) const
|
||||
{
|
||||
return values[keys.indexOf (key, ignoreCase)];
|
||||
}
|
||||
|
||||
String StringPairArray::getValue (StringRef key, const String& defaultReturnValue) const
|
||||
{
|
||||
auto i = keys.indexOf (key, ignoreCase);
|
||||
|
||||
if (i >= 0)
|
||||
return values[i];
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
bool StringPairArray::containsKey (StringRef key) const noexcept
|
||||
{
|
||||
return keys.contains (key);
|
||||
}
|
||||
|
||||
void StringPairArray::set (const String& key, const String& value)
|
||||
{
|
||||
auto i = keys.indexOf (key, ignoreCase);
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
values.set (i, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
keys.add (key);
|
||||
values.add (value);
|
||||
}
|
||||
}
|
||||
|
||||
void StringPairArray::addArray (const StringPairArray& other)
|
||||
{
|
||||
for (int i = 0; i < other.size(); ++i)
|
||||
set (other.keys[i], other.values[i]);
|
||||
}
|
||||
|
||||
void StringPairArray::clear()
|
||||
{
|
||||
keys.clear();
|
||||
values.clear();
|
||||
}
|
||||
|
||||
void StringPairArray::remove (StringRef key)
|
||||
{
|
||||
remove (keys.indexOf (key, ignoreCase));
|
||||
}
|
||||
|
||||
void StringPairArray::remove (int index)
|
||||
{
|
||||
keys.remove (index);
|
||||
values.remove (index);
|
||||
}
|
||||
|
||||
void StringPairArray::setIgnoresCase (bool shouldIgnoreCase)
|
||||
{
|
||||
ignoreCase = shouldIgnoreCase;
|
||||
}
|
||||
|
||||
String StringPairArray::getDescription() const
|
||||
{
|
||||
String s;
|
||||
|
||||
for (int i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
s << keys[i] << " = " << values[i];
|
||||
|
||||
if (i < keys.size())
|
||||
s << ", ";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void StringPairArray::minimiseStorageOverheads()
|
||||
{
|
||||
keys.minimiseStorageOverheads();
|
||||
values.minimiseStorageOverheads();
|
||||
}
|
||||
|
||||
} // namespace juce
|
151
modules/juce_core/text/juce_StringPairArray.h
Normal file
151
modules/juce_core/text/juce_StringPairArray.h
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A container for holding a set of strings which are keyed by another string.
|
||||
|
||||
@see StringArray
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API StringPairArray
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty array */
|
||||
StringPairArray (bool ignoreCaseWhenComparingKeys = true);
|
||||
|
||||
/** Creates a copy of another array */
|
||||
StringPairArray (const StringPairArray& other);
|
||||
|
||||
/** Destructor. */
|
||||
~StringPairArray();
|
||||
|
||||
/** Copies the contents of another string array into this one */
|
||||
StringPairArray& operator= (const StringPairArray& other);
|
||||
|
||||
//==============================================================================
|
||||
/** Compares two arrays.
|
||||
Comparisons are case-sensitive.
|
||||
@returns true only if the other array contains exactly the same strings with the same keys
|
||||
*/
|
||||
bool operator== (const StringPairArray& other) const;
|
||||
|
||||
/** Compares two arrays.
|
||||
Comparisons are case-sensitive.
|
||||
@returns false if the other array contains exactly the same strings with the same keys
|
||||
*/
|
||||
bool operator!= (const StringPairArray& other) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Finds the value corresponding to a key string.
|
||||
|
||||
If no such key is found, this will just return an empty string. To check whether
|
||||
a given key actually exists (because it might actually be paired with an empty string), use
|
||||
the getAllKeys() method to obtain a list.
|
||||
|
||||
Obviously the reference returned shouldn't be stored for later use, as the
|
||||
string it refers to may disappear when the array changes.
|
||||
|
||||
@see getValue
|
||||
*/
|
||||
const String& operator[] (StringRef key) const;
|
||||
|
||||
/** Finds the value corresponding to a key string.
|
||||
If no such key is found, this will just return the value provided as a default.
|
||||
@see operator[]
|
||||
*/
|
||||
String getValue (StringRef, const String& defaultReturnValue) const;
|
||||
|
||||
/** Returns true if the given key exists. */
|
||||
bool containsKey (StringRef key) const noexcept;
|
||||
|
||||
/** Returns a list of all keys in the array. */
|
||||
const StringArray& getAllKeys() const noexcept { return keys; }
|
||||
|
||||
/** Returns a list of all values in the array. */
|
||||
const StringArray& getAllValues() const noexcept { return values; }
|
||||
|
||||
/** Returns the number of strings in the array */
|
||||
inline int size() const noexcept { return keys.size(); }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Adds or amends a key/value pair.
|
||||
If a value already exists with this key, its value will be overwritten,
|
||||
otherwise the key/value pair will be added to the array.
|
||||
*/
|
||||
void set (const String& key, const String& value);
|
||||
|
||||
/** Adds the items from another array to this one.
|
||||
This is equivalent to using set() to add each of the pairs from the other array.
|
||||
*/
|
||||
void addArray (const StringPairArray& other);
|
||||
|
||||
//==============================================================================
|
||||
/** Removes all elements from the array. */
|
||||
void clear();
|
||||
|
||||
/** Removes a string from the array based on its key.
|
||||
If the key isn't found, nothing will happen.
|
||||
*/
|
||||
void remove (StringRef key);
|
||||
|
||||
/** Removes a string from the array based on its index.
|
||||
If the index is out-of-range, no action will be taken.
|
||||
*/
|
||||
void remove (int index);
|
||||
|
||||
//==============================================================================
|
||||
/** Indicates whether to use a case-insensitive search when looking up a key string.
|
||||
*/
|
||||
void setIgnoresCase (bool shouldIgnoreCase);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a descriptive string containing the items.
|
||||
This is handy for dumping the contents of an array.
|
||||
*/
|
||||
String getDescription() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Reduces the amount of storage being used by the array.
|
||||
|
||||
Arrays typically allocate slightly more storage than they need, and after
|
||||
removing elements, they may have quite a lot of unused space allocated.
|
||||
This method will reduce the amount of allocated storage to a minimum.
|
||||
*/
|
||||
void minimiseStorageOverheads();
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
StringArray keys, values;
|
||||
bool ignoreCase;
|
||||
|
||||
JUCE_LEAK_DETECTOR (StringPairArray)
|
||||
};
|
||||
|
||||
} // namespace juce
|
165
modules/juce_core/text/juce_StringPool.cpp
Normal file
165
modules/juce_core/text/juce_StringPool.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static const int minNumberOfStringsForGarbageCollection = 300;
|
||||
static const uint32 garbageCollectionInterval = 30000;
|
||||
|
||||
|
||||
StringPool::StringPool() noexcept : lastGarbageCollectionTime (0) {}
|
||||
StringPool::~StringPool() {}
|
||||
|
||||
struct StartEndString
|
||||
{
|
||||
StartEndString (String::CharPointerType s, String::CharPointerType e) noexcept : start (s), end (e) {}
|
||||
operator String() const { return String (start, end); }
|
||||
|
||||
String::CharPointerType start, end;
|
||||
};
|
||||
|
||||
static int compareStrings (const String& s1, const String& s2) noexcept { return s1.compare (s2); }
|
||||
static int compareStrings (CharPointer_UTF8 s1, const String& s2) noexcept { return s1.compare (s2.getCharPointer()); }
|
||||
|
||||
static int compareStrings (const StartEndString& string1, const String& string2) noexcept
|
||||
{
|
||||
String::CharPointerType s1 (string1.start), s2 (string2.getCharPointer());
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const int c1 = s1 < string1.end ? (int) s1.getAndAdvance() : 0;
|
||||
const int c2 = (int) s2.getAndAdvance();
|
||||
const int diff = c1 - c2;
|
||||
|
||||
if (diff != 0) return diff < 0 ? -1 : 1;
|
||||
if (c1 == 0) break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename NewStringType>
|
||||
static String addPooledString (Array<String>& strings, const NewStringType& newString)
|
||||
{
|
||||
int start = 0;
|
||||
int end = strings.size();
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
const String& startString = strings.getReference (start);
|
||||
const int startComp = compareStrings (newString, startString);
|
||||
|
||||
if (startComp == 0)
|
||||
return startString;
|
||||
|
||||
const int halfway = (start + end) / 2;
|
||||
|
||||
if (halfway == start)
|
||||
{
|
||||
if (startComp > 0)
|
||||
++start;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const String& halfwayString = strings.getReference (halfway);
|
||||
const int halfwayComp = compareStrings (newString, halfwayString);
|
||||
|
||||
if (halfwayComp == 0)
|
||||
return halfwayString;
|
||||
|
||||
if (halfwayComp > 0)
|
||||
start = halfway;
|
||||
else
|
||||
end = halfway;
|
||||
}
|
||||
|
||||
strings.insert (start, newString);
|
||||
return strings.getReference (start);
|
||||
}
|
||||
|
||||
String StringPool::getPooledString (const char* const newString)
|
||||
{
|
||||
if (newString == nullptr || *newString == 0)
|
||||
return {};
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
garbageCollectIfNeeded();
|
||||
return addPooledString (strings, CharPointer_UTF8 (newString));
|
||||
}
|
||||
|
||||
String StringPool::getPooledString (String::CharPointerType start, String::CharPointerType end)
|
||||
{
|
||||
if (start.isEmpty() || start == end)
|
||||
return {};
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
garbageCollectIfNeeded();
|
||||
return addPooledString (strings, StartEndString (start, end));
|
||||
}
|
||||
|
||||
String StringPool::getPooledString (StringRef newString)
|
||||
{
|
||||
if (newString.isEmpty())
|
||||
return {};
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
garbageCollectIfNeeded();
|
||||
return addPooledString (strings, newString.text);
|
||||
}
|
||||
|
||||
String StringPool::getPooledString (const String& newString)
|
||||
{
|
||||
if (newString.isEmpty())
|
||||
return {};
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
garbageCollectIfNeeded();
|
||||
return addPooledString (strings, newString);
|
||||
}
|
||||
|
||||
void StringPool::garbageCollectIfNeeded()
|
||||
{
|
||||
if (strings.size() > minNumberOfStringsForGarbageCollection
|
||||
&& Time::getApproximateMillisecondCounter() > lastGarbageCollectionTime + garbageCollectionInterval)
|
||||
garbageCollect();
|
||||
}
|
||||
|
||||
void StringPool::garbageCollect()
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
for (int i = strings.size(); --i >= 0;)
|
||||
if (strings.getReference(i).getReferenceCount() == 1)
|
||||
strings.remove (i);
|
||||
|
||||
lastGarbageCollectionTime = Time::getApproximateMillisecondCounter();
|
||||
}
|
||||
|
||||
StringPool& StringPool::getGlobalPool() noexcept
|
||||
{
|
||||
static StringPool pool;
|
||||
return pool;
|
||||
}
|
||||
|
||||
} // namespace juce
|
90
modules/juce_core/text/juce_StringPool.h
Normal file
90
modules/juce_core/text/juce_StringPool.h
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A StringPool holds a set of shared strings, which reduces storage overheads and improves
|
||||
comparison speed when dealing with many duplicate strings.
|
||||
|
||||
When you add a string to a pool using getPooledString, it'll return a character
|
||||
array containing the same string. This array is owned by the pool, and the same array
|
||||
is returned every time a matching string is asked for. This means that it's trivial to
|
||||
compare two pooled strings for equality, as you can simply compare their pointers. It
|
||||
also cuts down on storage if you're using many copies of the same string.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API StringPool
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty pool. */
|
||||
StringPool() noexcept;
|
||||
|
||||
/** Destructor */
|
||||
~StringPool();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a pointer to a shared copy of the string that is passed in.
|
||||
The pool will always return the same String object when asked for a string that matches it.
|
||||
*/
|
||||
String getPooledString (const String& original);
|
||||
|
||||
/** Returns a pointer to a copy of the string that is passed in.
|
||||
The pool will always return the same String object when asked for a string that matches it.
|
||||
*/
|
||||
String getPooledString (const char* original);
|
||||
|
||||
/** Returns a pointer to a shared copy of the string that is passed in.
|
||||
The pool will always return the same String object when asked for a string that matches it.
|
||||
*/
|
||||
String getPooledString (StringRef original);
|
||||
|
||||
/** Returns a pointer to a copy of the string that is passed in.
|
||||
The pool will always return the same String object when asked for a string that matches it.
|
||||
*/
|
||||
String getPooledString (String::CharPointerType start, String::CharPointerType end);
|
||||
|
||||
//==============================================================================
|
||||
/** Scans the pool, and removes any strings that are unreferenced.
|
||||
You don't generally need to call this - it'll be called automatically when the pool grows
|
||||
large enough to warrant it.
|
||||
*/
|
||||
void garbageCollect();
|
||||
|
||||
/** Returns a shared global pool which is used for things like Identifiers, XML parsing. */
|
||||
static StringPool& getGlobalPool() noexcept;
|
||||
|
||||
private:
|
||||
Array<String> strings;
|
||||
CriticalSection lock;
|
||||
uint32 lastGarbageCollectionTime;
|
||||
|
||||
void garbageCollectIfNeeded();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (StringPool)
|
||||
};
|
||||
|
||||
} // namespace juce
|
142
modules/juce_core/text/juce_StringRef.h
Normal file
142
modules/juce_core/text/juce_StringRef.h
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple class for holding temporary references to a string literal or String.
|
||||
|
||||
Unlike a real String object, the StringRef does not allocate any memory or
|
||||
take ownership of the strings you give to it - it simply holds a reference to
|
||||
a string that has been allocated elsewhere.
|
||||
The main purpose of the class is to be used instead of a const String& as the type
|
||||
of function arguments where the caller may pass either a string literal or a String
|
||||
object. This means that when the called uses a string literal, there's no need
|
||||
for an temporary String object to be allocated, and this cuts down overheads
|
||||
substantially.
|
||||
|
||||
Because the class is simply a wrapper around a pointer, you should always pass
|
||||
it by value, not by reference.
|
||||
|
||||
@code
|
||||
void myStringFunction1 (const String&);
|
||||
void myStringFunction2 (StringRef);
|
||||
|
||||
myStringFunction1 ("abc"); // Implicitly allocates a temporary String object.
|
||||
myStringFunction2 ("abc"); // Much faster, as no local allocations are needed.
|
||||
@endcode
|
||||
|
||||
For examples of it in use, see the XmlElement or StringArray classes.
|
||||
|
||||
Bear in mind that there are still many cases where it's better to use an argument
|
||||
which is a const String&. For example if the function stores the string or needs
|
||||
to internally create a String from the argument, then it's better for the original
|
||||
argument to already be a String.
|
||||
|
||||
@see String
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API StringRef final
|
||||
{
|
||||
public:
|
||||
/** Creates a StringRef from a raw string literal.
|
||||
The StringRef object does NOT take ownership or copy this data, so you must
|
||||
ensure that the data does not change during the lifetime of the StringRef.
|
||||
Note that this pointer cannot be null!
|
||||
*/
|
||||
StringRef (const char* stringLiteral) noexcept;
|
||||
|
||||
/** Creates a StringRef from a raw char pointer.
|
||||
The StringRef object does NOT take ownership or copy this data, so you must
|
||||
ensure that the data does not change during the lifetime of the StringRef.
|
||||
*/
|
||||
StringRef (String::CharPointerType stringLiteral) noexcept;
|
||||
|
||||
/** Creates a StringRef from a String.
|
||||
The StringRef object does NOT take ownership or copy the data from the String,
|
||||
so you must ensure that the String is not modified or deleted during the lifetime
|
||||
of the StringRef.
|
||||
*/
|
||||
StringRef (const String& string) noexcept;
|
||||
|
||||
/** Creates a StringRef from a String.
|
||||
The StringRef object does NOT take ownership or copy the data from the std::string,
|
||||
so you must ensure that the source string object is not modified or deleted during
|
||||
the lifetime of the StringRef.
|
||||
*/
|
||||
StringRef (const std::string& string);
|
||||
|
||||
/** Creates a StringRef pointer to an empty string. */
|
||||
StringRef() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a raw pointer to the underlying string data. */
|
||||
operator const String::CharPointerType::CharType*() const noexcept { return text.getAddress(); }
|
||||
/** Returns a pointer to the underlying string data as a char pointer object. */
|
||||
operator String::CharPointerType() const noexcept { return text; }
|
||||
|
||||
/** Returns true if the string is empty. */
|
||||
bool isEmpty() const noexcept { return text.isEmpty(); }
|
||||
/** Returns true if the string is not empty. */
|
||||
bool isNotEmpty() const noexcept { return ! text.isEmpty(); }
|
||||
/** Returns the number of characters in the string. */
|
||||
int length() const noexcept { return (int) text.length(); }
|
||||
|
||||
/** Retrieves a character by index. */
|
||||
juce_wchar operator[] (int index) const noexcept { return text[index]; }
|
||||
|
||||
/** Compares this StringRef with a String. */
|
||||
bool operator== (const String& s) const noexcept { return text.compare (s.getCharPointer()) == 0; }
|
||||
/** Compares this StringRef with a String. */
|
||||
bool operator!= (const String& s) const noexcept { return text.compare (s.getCharPointer()) != 0; }
|
||||
|
||||
/** Case-sensitive comparison of two StringRefs. */
|
||||
bool operator== (StringRef s) const noexcept { return text.compare (s.text) == 0; }
|
||||
/** Case-sensitive comparison of two StringRefs. */
|
||||
bool operator!= (StringRef s) const noexcept { return text.compare (s.text) != 0; }
|
||||
|
||||
//==============================================================================
|
||||
/** The text that is referenced. */
|
||||
String::CharPointerType text;
|
||||
|
||||
#if JUCE_STRING_UTF_TYPE != 8 && ! defined (DOXYGEN)
|
||||
// Sorry, non-UTF8 people, you're unable to take advantage of StringRef, because
|
||||
// you've chosen a character encoding that doesn't match C++ string literals.
|
||||
String stringCopy;
|
||||
#endif
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Case-sensitive comparison of two strings. */
|
||||
JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, StringRef string2) noexcept;
|
||||
/** Case-sensitive comparison of two strings. */
|
||||
JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, StringRef string2) noexcept;
|
||||
|
||||
inline String operator+ (String s1, StringRef s2) { return s1 += String (s2.text); }
|
||||
inline String operator+ (StringRef s1, const String& s2) { return String (s1.text) + s2; }
|
||||
inline String operator+ (const char* s1, StringRef s2) { return String (s1) + String (s2.text); }
|
||||
inline String operator+ (StringRef s1, const char* s2) { return String (s1.text) + String (s2); }
|
||||
|
||||
} // namespace juce
|
283
modules/juce_core/text/juce_TextDiff.cpp
Normal file
283
modules/juce_core/text/juce_TextDiff.cpp
Normal file
@ -0,0 +1,283 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct TextDiffHelpers
|
||||
{
|
||||
enum { minLengthToMatch = 3,
|
||||
maxComplexity = 16 * 1024 * 1024 };
|
||||
|
||||
struct StringRegion
|
||||
{
|
||||
StringRegion (const String& s) noexcept
|
||||
: text (s.getCharPointer()), start (0), length (s.length()) {}
|
||||
|
||||
StringRegion (String::CharPointerType t, int s, int len) noexcept
|
||||
: text (t), start (s), length (len) {}
|
||||
|
||||
void incrementStart() noexcept { ++text; ++start; --length; }
|
||||
|
||||
String::CharPointerType text;
|
||||
int start, length;
|
||||
};
|
||||
|
||||
static void addInsertion (TextDiff& td, String::CharPointerType text, int index, int length)
|
||||
{
|
||||
TextDiff::Change c;
|
||||
c.insertedText = String (text, (size_t) length);
|
||||
c.start = index;
|
||||
c.length = 0;
|
||||
td.changes.add (c);
|
||||
}
|
||||
|
||||
static void addDeletion (TextDiff& td, int index, int length)
|
||||
{
|
||||
TextDiff::Change c;
|
||||
c.start = index;
|
||||
c.length = length;
|
||||
td.changes.add (c);
|
||||
}
|
||||
|
||||
static void diffSkippingCommonStart (TextDiff& td, StringRegion a, StringRegion b)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
auto ca = *a.text;
|
||||
auto cb = *b.text;
|
||||
|
||||
if (ca != cb || ca == 0)
|
||||
break;
|
||||
|
||||
a.incrementStart();
|
||||
b.incrementStart();
|
||||
}
|
||||
|
||||
diffRecursively (td, a, b);
|
||||
}
|
||||
|
||||
static void diffRecursively (TextDiff& td, StringRegion a, StringRegion b)
|
||||
{
|
||||
int indexA = 0, indexB = 0;
|
||||
auto len = findLongestCommonSubstring (a.text, a.length, indexA,
|
||||
b.text, b.length, indexB);
|
||||
|
||||
if (len >= minLengthToMatch)
|
||||
{
|
||||
if (indexA > 0 && indexB > 0)
|
||||
diffSkippingCommonStart (td, StringRegion (a.text, a.start, indexA),
|
||||
StringRegion (b.text, b.start, indexB));
|
||||
else if (indexA > 0)
|
||||
addDeletion (td, b.start, indexA);
|
||||
else if (indexB > 0)
|
||||
addInsertion (td, b.text, b.start, indexB);
|
||||
|
||||
diffRecursively (td, StringRegion (a.text + (indexA + len), a.start + indexA + len, a.length - indexA - len),
|
||||
StringRegion (b.text + (indexB + len), b.start + indexB + len, b.length - indexB - len));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a.length > 0) addDeletion (td, b.start, a.length);
|
||||
if (b.length > 0) addInsertion (td, b.text, b.start, b.length);
|
||||
}
|
||||
}
|
||||
|
||||
static int findLongestCommonSubstring (String::CharPointerType a, const int lenA, int& indexInA,
|
||||
String::CharPointerType b, const int lenB, int& indexInB) noexcept
|
||||
{
|
||||
if (lenA == 0 || lenB == 0)
|
||||
return 0;
|
||||
|
||||
if (lenA * lenB > maxComplexity)
|
||||
return findCommonSuffix (a, lenA, indexInA,
|
||||
b, lenB, indexInB);
|
||||
|
||||
auto scratchSpace = sizeof (int) * (2 + 2 * (size_t) lenB);
|
||||
|
||||
if (scratchSpace < 4096)
|
||||
{
|
||||
auto* scratch = (int*) alloca (scratchSpace);
|
||||
return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch);
|
||||
}
|
||||
|
||||
HeapBlock<int> scratch (scratchSpace);
|
||||
return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch);
|
||||
}
|
||||
|
||||
static int findLongestCommonSubstring (String::CharPointerType a, const int lenA, int& indexInA,
|
||||
String::CharPointerType b, const int lenB, int& indexInB,
|
||||
const size_t scratchSpace, int* const lines) noexcept
|
||||
{
|
||||
zeromem (lines, scratchSpace);
|
||||
|
||||
auto* l0 = lines;
|
||||
auto* l1 = l0 + lenB + 1;
|
||||
|
||||
int loopsWithoutImprovement = 0;
|
||||
int bestLength = 0;
|
||||
|
||||
for (int i = 0; i < lenA; ++i)
|
||||
{
|
||||
auto ca = a.getAndAdvance();
|
||||
auto b2 = b;
|
||||
|
||||
for (int j = 0; j < lenB; ++j)
|
||||
{
|
||||
if (ca != b2.getAndAdvance())
|
||||
{
|
||||
l1[j + 1] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto len = l0[j] + 1;
|
||||
l1[j + 1] = len;
|
||||
|
||||
if (len > bestLength)
|
||||
{
|
||||
loopsWithoutImprovement = 0;
|
||||
bestLength = len;
|
||||
indexInA = i;
|
||||
indexInB = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (++loopsWithoutImprovement > 100)
|
||||
break;
|
||||
|
||||
std::swap (l0, l1);
|
||||
}
|
||||
|
||||
indexInA -= bestLength - 1;
|
||||
indexInB -= bestLength - 1;
|
||||
return bestLength;
|
||||
}
|
||||
|
||||
static int findCommonSuffix (String::CharPointerType a, int lenA, int& indexInA,
|
||||
String::CharPointerType b, int lenB, int& indexInB) noexcept
|
||||
{
|
||||
int length = 0;
|
||||
a += lenA - 1;
|
||||
b += lenB - 1;
|
||||
|
||||
while (length < lenA && length < lenB && *a == *b)
|
||||
{
|
||||
--a;
|
||||
--b;
|
||||
++length;
|
||||
}
|
||||
|
||||
indexInA = lenA - length;
|
||||
indexInB = lenB - length;
|
||||
return length;
|
||||
}
|
||||
};
|
||||
|
||||
TextDiff::TextDiff (const String& original, const String& target)
|
||||
{
|
||||
TextDiffHelpers::diffSkippingCommonStart (*this, original, target);
|
||||
}
|
||||
|
||||
String TextDiff::appliedTo (String text) const
|
||||
{
|
||||
for (auto& c : changes)
|
||||
text = c.appliedTo (text);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
bool TextDiff::Change::isDeletion() const noexcept
|
||||
{
|
||||
return insertedText.isEmpty();
|
||||
}
|
||||
|
||||
String TextDiff::Change::appliedTo (const String& text) const noexcept
|
||||
{
|
||||
return text.replaceSection (start, length, insertedText);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class DiffTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
DiffTests() : UnitTest ("TextDiff class", "Text") {}
|
||||
|
||||
static String createString (Random& r)
|
||||
{
|
||||
juce_wchar buffer[500] = { 0 };
|
||||
|
||||
for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
|
||||
{
|
||||
if (r.nextInt (10) == 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
|
||||
}
|
||||
while (! CharPointer_UTF16::canRepresent (buffer[i]));
|
||||
}
|
||||
else
|
||||
buffer[i] = (juce_wchar) ('a' + r.nextInt (3));
|
||||
}
|
||||
|
||||
return CharPointer_UTF32 (buffer);
|
||||
}
|
||||
|
||||
void testDiff (const String& a, const String& b)
|
||||
{
|
||||
TextDiff diff (a, b);
|
||||
auto result = diff.appliedTo (a);
|
||||
expectEquals (result, b);
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("TextDiff");
|
||||
|
||||
auto r = getRandom();
|
||||
|
||||
testDiff (String(), String());
|
||||
testDiff ("x", String());
|
||||
testDiff (String(), "x");
|
||||
testDiff ("x", "x");
|
||||
testDiff ("x", "y");
|
||||
testDiff ("xxx", "x");
|
||||
testDiff ("x", "xxx");
|
||||
|
||||
for (int i = 1000; --i >= 0;)
|
||||
{
|
||||
auto s = createString (r);
|
||||
testDiff (s, createString (r));
|
||||
testDiff (s + createString (r), s + createString (r));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static DiffTests diffTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
74
modules/juce_core/text/juce_TextDiff.h
Normal file
74
modules/juce_core/text/juce_TextDiff.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Calculates and applies a sequence of changes to convert one text string into
|
||||
another.
|
||||
|
||||
Once created, the TextDiff object contains an array of change objects, where
|
||||
each change can be either an insertion or a deletion. When applied in order
|
||||
to the original string, these changes will convert it to the target string.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API TextDiff
|
||||
{
|
||||
public:
|
||||
/** Creates a set of diffs for converting the original string into the target. */
|
||||
TextDiff (const String& original,
|
||||
const String& target);
|
||||
|
||||
/** Applies this sequence of changes to the original string, producing the
|
||||
target string that was specified when generating them.
|
||||
|
||||
Obviously it only makes sense to call this function with the string that
|
||||
was originally passed to the constructor. Any other input will produce an
|
||||
undefined result.
|
||||
*/
|
||||
String appliedTo (String text) const;
|
||||
|
||||
/** Describes a change, which can be either an insertion or deletion. */
|
||||
struct Change
|
||||
{
|
||||
String insertedText; /**< If this change is a deletion, this string will be empty; otherwise,
|
||||
it'll be the text that should be inserted at the index specified by start. */
|
||||
int start; /**< Specifies the character index in a string at which text should be inserted or deleted. */
|
||||
int length; /**< If this change is a deletion, this specifies the number of characters to delete. For an
|
||||
insertion, this is the length of the new text being inserted. */
|
||||
|
||||
/** Returns true if this change is a deletion, or false for an insertion. */
|
||||
bool isDeletion() const noexcept;
|
||||
|
||||
/** Returns the result of applying this change to a string. */
|
||||
String appliedTo (const String& original) const noexcept;
|
||||
};
|
||||
|
||||
/** The list of changes required to perform the transformation.
|
||||
Applying each of these, in order, to the original string will produce the target.
|
||||
*/
|
||||
Array<Change> changes;
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user