416 lines
12 KiB
C++
416 lines
12 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file textEncoder.cxx
|
|
* @author drose
|
|
* @date 2003-03-26
|
|
*/
|
|
|
|
#include "textEncoder.h"
|
|
#include "stringDecoder.h"
|
|
#include "unicodeLatinMap.h"
|
|
#include "config_dtoolutil.h"
|
|
|
|
using std::istream;
|
|
using std::ostream;
|
|
using std::string;
|
|
using std::wstring;
|
|
|
|
TextEncoder::Encoding TextEncoder::_default_encoding = TextEncoder::E_utf8;
|
|
|
|
/**
|
|
* Adjusts the text stored within the encoder to all uppercase letters
|
|
* (preserving accent marks correctly).
|
|
*/
|
|
void TextEncoder::
|
|
make_upper() {
|
|
get_wtext();
|
|
wstring::iterator si;
|
|
for (si = _wtext.begin(); si != _wtext.end(); ++si) {
|
|
(*si) = unicode_toupper(*si);
|
|
}
|
|
_flags &= ~F_got_text;
|
|
text_changed();
|
|
}
|
|
|
|
/**
|
|
* Adjusts the text stored within the encoder to all lowercase letters
|
|
* (preserving accent marks correctly).
|
|
*/
|
|
void TextEncoder::
|
|
make_lower() {
|
|
get_wtext();
|
|
wstring::iterator si;
|
|
for (si = _wtext.begin(); si != _wtext.end(); ++si) {
|
|
(*si) = unicode_tolower(*si);
|
|
}
|
|
_flags &= ~F_got_text;
|
|
text_changed();
|
|
}
|
|
|
|
/**
|
|
* Returns the text associated with the node, converted as nearly as possible
|
|
* to a fully-ASCII representation. This means replacing accented letters
|
|
* with their unaccented ASCII equivalents.
|
|
*
|
|
* It is possible that some characters in the string cannot be converted to
|
|
* ASCII. (The string may involve symbols like the copyright symbol, for
|
|
* instance, or it might involve letters in some other alphabet such as Greek
|
|
* or Cyrillic, or even Latin letters like thorn or eth that are not part of
|
|
* the ASCII character set.) In this case, as much of the string as possible
|
|
* will be converted to ASCII, and the nonconvertible characters will remain
|
|
* in their original form.
|
|
*/
|
|
wstring TextEncoder::
|
|
get_wtext_as_ascii() const {
|
|
get_wtext();
|
|
wstring result;
|
|
wstring::const_iterator si;
|
|
for (si = _wtext.begin(); si != _wtext.end(); ++si) {
|
|
wchar_t character = (*si);
|
|
|
|
const UnicodeLatinMap::Entry *map_entry =
|
|
UnicodeLatinMap::look_up(character);
|
|
if (map_entry != nullptr && map_entry->_ascii_equiv != 0) {
|
|
result += (wchar_t)map_entry->_ascii_equiv;
|
|
if (map_entry->_ascii_additional != 0) {
|
|
result += (wchar_t)map_entry->_ascii_additional;
|
|
}
|
|
|
|
} else {
|
|
result += character;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns true if any of the characters in the string returned by get_wtext()
|
|
* are out of the range of an ASCII character (and, therefore, get_wtext()
|
|
* should be called in preference to get_text()).
|
|
*/
|
|
bool TextEncoder::
|
|
is_wtext() const {
|
|
get_wtext();
|
|
wstring::const_iterator ti;
|
|
for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) {
|
|
if (((*ti) & ~0x7f) != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Encodes a single Unicode character into a one-, two-, three-, or four-byte
|
|
* string, according to the given encoding system.
|
|
*/
|
|
string TextEncoder::
|
|
encode_wchar(char32_t ch, TextEncoder::Encoding encoding) {
|
|
switch (encoding) {
|
|
case E_iso8859:
|
|
if ((ch & ~0xff) == 0) {
|
|
return string(1, (char)ch);
|
|
} else {
|
|
// The character won't fit in the 8-bit ISO 8859. See if we can make it
|
|
// fit by reducing it to its ascii equivalent (essentially stripping off
|
|
// an unusual accent mark).
|
|
const UnicodeLatinMap::Entry *map_entry =
|
|
UnicodeLatinMap::look_up(ch);
|
|
if (map_entry != nullptr && map_entry->_ascii_equiv != 0) {
|
|
// Yes, it has an ascii equivalent.
|
|
if (map_entry->_ascii_additional != 0) {
|
|
// In fact, it has two of them.
|
|
return
|
|
string(1, map_entry->_ascii_equiv) +
|
|
string(1, map_entry->_ascii_additional);
|
|
}
|
|
return string(1, map_entry->_ascii_equiv);
|
|
}
|
|
// Nope; return "." for lack of anything better.
|
|
return ".";
|
|
}
|
|
|
|
case E_utf8:
|
|
if ((ch & ~0x7f) == 0) {
|
|
return string(1, (char)ch);
|
|
} else if ((ch & ~0x7ff) == 0) {
|
|
return
|
|
string(1, (char)((ch >> 6) | 0xc0)) +
|
|
string(1, (char)((ch & 0x3f) | 0x80));
|
|
} else if ((ch & ~0xffff) == 0) {
|
|
return
|
|
string(1, (char)((ch >> 12) | 0xe0)) +
|
|
string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
|
|
string(1, (char)((ch & 0x3f) | 0x80));
|
|
} else {
|
|
return
|
|
string(1, (char)((ch >> 18) | 0xf0)) +
|
|
string(1, (char)(((ch >> 12) & 0x3f) | 0x80)) +
|
|
string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
|
|
string(1, (char)((ch & 0x3f) | 0x80));
|
|
}
|
|
|
|
case E_utf16be:
|
|
if ((ch & ~0xffff) == 0) {
|
|
// Note that this passes through surrogates and BOMs unharmed.
|
|
return
|
|
string(1, (char)(ch >> 8)) +
|
|
string(1, (char)(ch & 0xff));
|
|
} else {
|
|
// Use a surrogate pair.
|
|
uint32_t v = (uint32_t)ch - 0x10000u;
|
|
uint16_t hi = (v >> 10u) | 0xd800u;
|
|
uint16_t lo = (v & 0x3ffu) | 0xdc00u;
|
|
char encoded[4] = {
|
|
(char)(hi >> 8),
|
|
(char)(hi & 0xff),
|
|
(char)(lo >> 8),
|
|
(char)(lo & 0xff),
|
|
};
|
|
return string(encoded, 4);
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Encodes a wide-text string into a single-char string, according to the
|
|
* given encoding.
|
|
*/
|
|
string TextEncoder::
|
|
encode_wtext(const wstring &wtext, TextEncoder::Encoding encoding) {
|
|
string result;
|
|
|
|
for (size_t i = 0; i < wtext.size(); ++i) {
|
|
wchar_t ch = wtext[i];
|
|
|
|
// On some systems, wstring may be UTF-16, and contain surrogate pairs.
|
|
#if WCHAR_MAX < 0x10FFFF
|
|
if (ch >= 0xd800 && ch < 0xdc00 && (i + 1) < wtext.size()) {
|
|
// This is a high surrogate. Look for a subsequent low surrogate.
|
|
wchar_t ch2 = wtext[i + 1];
|
|
if (ch2 >= 0xdc00 && ch2 < 0xe000) {
|
|
// Yes, this is a low surrogate.
|
|
char32_t code_point = 0x10000 + ((ch - 0xd800) << 10) + (ch2 - 0xdc00);
|
|
result += encode_wchar(code_point, encoding);
|
|
i++;
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
result += encode_wchar(ch, encoding);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the given wstring decoded to a single-byte string, via the given
|
|
* encoding system.
|
|
*/
|
|
wstring TextEncoder::
|
|
decode_text(const string &text, TextEncoder::Encoding encoding) {
|
|
switch (encoding) {
|
|
case E_utf8:
|
|
{
|
|
StringUtf8Decoder decoder(text);
|
|
return decode_text_impl(decoder);
|
|
}
|
|
|
|
case E_utf16be:
|
|
{
|
|
StringUtf16Decoder decoder(text);
|
|
return decode_text_impl(decoder);
|
|
}
|
|
|
|
case E_iso8859:
|
|
default:
|
|
{
|
|
StringDecoder decoder(text);
|
|
return decode_text_impl(decoder);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Decodes the eight-bit stream from the indicated decoder, returning the
|
|
* decoded wide-char string.
|
|
*/
|
|
wstring TextEncoder::
|
|
decode_text_impl(StringDecoder &decoder) {
|
|
wstring result;
|
|
// bool expand_amp = get_expand_amp();
|
|
|
|
char32_t character = decoder.get_next_character();
|
|
while (!decoder.is_eof()) {
|
|
/*
|
|
if (character == '&' && expand_amp) {
|
|
// An ampersand in expand_amp mode is treated as an escape character.
|
|
character = expand_amp_sequence(decoder);
|
|
}
|
|
*/
|
|
if (character <= WCHAR_MAX) {
|
|
result += character;
|
|
} else {
|
|
// We need to encode this as a surrogate pair.
|
|
uint32_t v = (uint32_t)character - 0x10000u;
|
|
result += (wchar_t)((v >> 10u) | 0xd800u);
|
|
result += (wchar_t)((v & 0x3ffu) | 0xdc00u);
|
|
}
|
|
character = decoder.get_next_character();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Given that we have just read an ampersand from the StringDecoder, and that
|
|
* we have expand_amp in effect and are therefore expected to expand the
|
|
* sequence that this ampersand begins into a single unicode character, do the
|
|
* expansion and return the character.
|
|
*/
|
|
/*
|
|
int TextEncoder::
|
|
expand_amp_sequence(StringDecoder &decoder) const {
|
|
int result = 0;
|
|
|
|
int character = decoder.get_next_character();
|
|
if (!decoder.is_eof() && character == '#') {
|
|
// An explicit numeric sequence: &#nnn;
|
|
result = 0;
|
|
character = decoder.get_next_character();
|
|
while (!decoder.is_eof() && character < 128 && isdigit((unsigned int)character)) {
|
|
result = (result * 10) + (character - '0');
|
|
character = decoder.get_next_character();
|
|
}
|
|
if (character != ';') {
|
|
// Invalid sequence.
|
|
return 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
string sequence;
|
|
|
|
// Some non-numeric sequence.
|
|
while (!decoder.is_eof() && character < 128 && isalpha((unsigned int)character)) {
|
|
sequence += character;
|
|
character = decoder.get_next_character();
|
|
}
|
|
if (character != ';') {
|
|
// Invalid sequence.
|
|
return 0;
|
|
}
|
|
|
|
static const struct {
|
|
const char *name;
|
|
int code;
|
|
} tokens[] = {
|
|
{ "amp", '&' }, { "lt", '<' }, { "gt", '>' }, { "quot", '"' },
|
|
{ "nbsp", ' ' },
|
|
|
|
{ "iexcl", 161 }, { "cent", 162 }, { "pound", 163 }, { "curren", 164 },
|
|
{ "yen", 165 }, { "brvbar", 166 }, { "brkbar", 166 }, { "sect", 167 },
|
|
{ "uml", 168 }, { "die", 168 }, { "copy", 169 }, { "ordf", 170 },
|
|
{ "laquo", 171 }, { "not", 172 }, { "shy", 173 }, { "reg", 174 },
|
|
{ "macr", 175 }, { "hibar", 175 }, { "deg", 176 }, { "plusmn", 177 },
|
|
{ "sup2", 178 }, { "sup3", 179 }, { "acute", 180 }, { "micro", 181 },
|
|
{ "para", 182 }, { "middot", 183 }, { "cedil", 184 }, { "sup1", 185 },
|
|
{ "ordm", 186 }, { "raquo", 187 }, { "frac14", 188 }, { "frac12", 189 },
|
|
{ "frac34", 190 }, { "iquest", 191 }, { "Agrave", 192 }, { "Aacute", 193 },
|
|
{ "Acirc", 194 }, { "Atilde", 195 }, { "Auml", 196 }, { "Aring", 197 },
|
|
{ "AElig", 198 }, { "Ccedil", 199 }, { "Egrave", 200 }, { "Eacute", 201 },
|
|
{ "Ecirc", 202 }, { "Euml", 203 }, { "Igrave", 204 }, { "Iacute", 205 },
|
|
{ "Icirc", 206 }, { "Iuml", 207 }, { "ETH", 208 }, { "Dstrok", 208 },
|
|
{ "Ntilde", 209 }, { "Ograve", 210 }, { "Oacute", 211 }, { "Ocirc", 212 },
|
|
{ "Otilde", 213 }, { "Ouml", 214 }, { "times", 215 }, { "Oslash", 216 },
|
|
{ "Ugrave", 217 }, { "Uacute", 218 }, { "Ucirc", 219 }, { "Uuml", 220 },
|
|
{ "Yacute", 221 }, { "THORN", 222 }, { "szlig", 223 }, { "agrave", 224 },
|
|
{ "aacute", 225 }, { "acirc", 226 }, { "atilde", 227 }, { "auml", 228 },
|
|
{ "aring", 229 }, { "aelig", 230 }, { "ccedil", 231 }, { "egrave", 232 },
|
|
{ "eacute", 233 }, { "ecirc", 234 }, { "euml", 235 }, { "igrave", 236 },
|
|
{ "iacute", 237 }, { "icirc", 238 }, { "iuml", 239 }, { "eth", 240 },
|
|
{ "ntilde", 241 }, { "ograve", 242 }, { "oacute", 243 }, { "ocirc", 244 },
|
|
{ "otilde", 245 }, { "ouml", 246 }, { "divide", 247 }, { "oslash", 248 },
|
|
{ "ugrave", 249 }, { "uacute", 250 }, { "ucirc", 251 }, { "uuml", 252 },
|
|
{ "yacute", 253 }, { "thorn", 254 }, { "yuml", 255 },
|
|
|
|
{ NULL, 0 },
|
|
};
|
|
|
|
for (int i = 0; tokens[i].name != NULL; i++) {
|
|
if (sequence == tokens[i].name) {
|
|
// Here's a match.
|
|
return tokens[i].code;
|
|
}
|
|
}
|
|
|
|
// Some unrecognized sequence.
|
|
return 0;
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Called whenever the text has been changed.
|
|
*/
|
|
void TextEncoder::
|
|
text_changed() {
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
ostream &
|
|
operator << (ostream &out, TextEncoder::Encoding encoding) {
|
|
switch (encoding) {
|
|
case TextEncoder::E_iso8859:
|
|
return out << "iso8859";
|
|
|
|
case TextEncoder::E_utf8:
|
|
return out << "utf8";
|
|
|
|
case TextEncoder::E_utf16be:
|
|
return out << "utf16be";
|
|
};
|
|
|
|
return out << "**invalid TextEncoder::Encoding(" << (int)encoding << ")**";
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
istream &
|
|
operator >> (istream &in, TextEncoder::Encoding &encoding) {
|
|
string word;
|
|
in >> word;
|
|
|
|
if (word == "iso8859") {
|
|
encoding = TextEncoder::E_iso8859;
|
|
} else if (word == "utf8" || word == "utf-8") {
|
|
encoding = TextEncoder::E_utf8;
|
|
} else if (word == "unicode" || word == "utf16be" || word == "utf-16be" ||
|
|
word == "utf16-be" || word == "utf-16-be") {
|
|
encoding = TextEncoder::E_utf16be;
|
|
} else {
|
|
ostream *notify_ptr = StringDecoder::get_notify_ptr();
|
|
if (notify_ptr != nullptr) {
|
|
(*notify_ptr)
|
|
<< "Invalid TextEncoder::Encoding: " << word << "\n";
|
|
}
|
|
encoding = TextEncoder::E_iso8859;
|
|
}
|
|
|
|
return in;
|
|
}
|