diff options
Diffstat (limited to 'core/string')
| -rw-r--r-- | core/string/SCsub | 7 | ||||
| -rw-r--r-- | core/string/compressed_translation.cpp | 288 | ||||
| -rw-r--r-- | core/string/compressed_translation.h | 89 | ||||
| -rw-r--r-- | core/string/node_path.cpp | 429 | ||||
| -rw-r--r-- | core/string/node_path.h | 99 | ||||
| -rw-r--r-- | core/string/print_string.cpp | 110 | ||||
| -rw-r--r-- | core/string/print_string.h | 58 | ||||
| -rw-r--r-- | core/string/string_buffer.h | 162 | ||||
| -rw-r--r-- | core/string/string_builder.cpp | 99 | ||||
| -rw-r--r-- | core/string/string_builder.h | 84 | ||||
| -rw-r--r-- | core/string/string_name.cpp | 393 | ||||
| -rw-r--r-- | core/string/string_name.h | 164 | ||||
| -rw-r--r-- | core/string/translation.cpp | 1303 | ||||
| -rw-r--r-- | core/string/translation.h | 129 | ||||
| -rw-r--r-- | core/string/translation_po.cpp | 312 | ||||
| -rw-r--r-- | core/string/translation_po.h | 92 | ||||
| -rw-r--r-- | core/string/ucaps.h | 1415 | ||||
| -rw-r--r-- | core/string/ustring.cpp | 4898 | ||||
| -rw-r--r-- | core/string/ustring.h | 558 | 
19 files changed, 10689 insertions, 0 deletions
diff --git a/core/string/SCsub b/core/string/SCsub new file mode 100644 index 0000000000..3217166f18 --- /dev/null +++ b/core/string/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +Import("env") + +env_string = env.Clone() + +env_string.add_source_files(env.core_sources, "*.cpp") diff --git a/core/string/compressed_translation.cpp b/core/string/compressed_translation.cpp new file mode 100644 index 0000000000..bdb296a79b --- /dev/null +++ b/core/string/compressed_translation.cpp @@ -0,0 +1,288 @@ +/*************************************************************************/ +/*  compressed_translation.cpp                                           */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "compressed_translation.h" + +#include "core/templates/pair.h" + +extern "C" { +#include "thirdparty/misc/smaz.h" +} + +struct _PHashTranslationCmp { +	int orig_len; +	CharString compressed; +	int offset; +}; + +void PHashTranslation::generate(const Ref<Translation> &p_from) { +	// This method compresses a Translation instance. +	// Right now it doesn't handle context or plurals, so Translation subclasses using plurals or context (i.e TranslationPO) shouldn't be compressed. +#ifdef TOOLS_ENABLED +	List<StringName> keys; +	p_from->get_message_list(&keys); + +	int size = Math::larger_prime(keys.size()); + +	Vector<Vector<Pair<int, CharString>>> buckets; +	Vector<Map<uint32_t, int>> table; +	Vector<uint32_t> hfunc_table; +	Vector<_PHashTranslationCmp> compressed; + +	table.resize(size); +	hfunc_table.resize(size); +	buckets.resize(size); +	compressed.resize(keys.size()); + +	int idx = 0; +	int total_compression_size = 0; +	int total_string_size = 0; + +	for (List<StringName>::Element *E = keys.front(); E; E = E->next()) { +		//hash string +		CharString cs = E->get().operator String().utf8(); +		uint32_t h = hash(0, cs.get_data()); +		Pair<int, CharString> p; +		p.first = idx; +		p.second = cs; +		buckets.write[h % size].push_back(p); + +		//compress string +		CharString src_s = p_from->get_message(E->get()).operator String().utf8(); +		_PHashTranslationCmp ps; +		ps.orig_len = src_s.size(); +		ps.offset = total_compression_size; + +		if (ps.orig_len != 0) { +			CharString dst_s; +			dst_s.resize(src_s.size()); +			int ret = smaz_compress(src_s.get_data(), src_s.size(), dst_s.ptrw(), src_s.size()); +			if (ret >= src_s.size()) { +				//if compressed is larger than original, just use original +				ps.orig_len = src_s.size(); +				ps.compressed = src_s; +			} else { +				dst_s.resize(ret); +				//ps.orig_len=; +				ps.compressed = dst_s; +			} +		} else { +			ps.orig_len = 1; +			ps.compressed.resize(1); +			ps.compressed[0] = 0; +		} + +		compressed.write[idx] = ps; +		total_compression_size += ps.compressed.size(); +		total_string_size += src_s.size(); +		idx++; +	} + +	int bucket_table_size = 0; + +	for (int i = 0; i < size; i++) { +		const Vector<Pair<int, CharString>> &b = buckets[i]; +		Map<uint32_t, int> &t = table.write[i]; + +		if (b.size() == 0) { +			continue; +		} + +		int d = 1; +		int item = 0; + +		while (item < b.size()) { +			uint32_t slot = hash(d, b[item].second.get_data()); +			if (t.has(slot)) { +				item = 0; +				d++; +				t.clear(); +			} else { +				t[slot] = b[item].first; +				item++; +			} +		} + +		hfunc_table.write[i] = d; +		bucket_table_size += 2 + b.size() * 4; +	} + +	ERR_FAIL_COND(bucket_table_size == 0); + +	hash_table.resize(size); +	bucket_table.resize(bucket_table_size); + +	int *htwb = hash_table.ptrw(); +	int *btwb = bucket_table.ptrw(); + +	uint32_t *htw = (uint32_t *)&htwb[0]; +	uint32_t *btw = (uint32_t *)&btwb[0]; + +	int btindex = 0; +	int collisions = 0; + +	for (int i = 0; i < size; i++) { +		const Map<uint32_t, int> &t = table[i]; +		if (t.size() == 0) { +			htw[i] = 0xFFFFFFFF; //nothing +			continue; +		} else if (t.size() > 1) { +			collisions += t.size() - 1; +		} + +		htw[i] = btindex; +		btw[btindex++] = t.size(); +		btw[btindex++] = hfunc_table[i]; + +		for (Map<uint32_t, int>::Element *E = t.front(); E; E = E->next()) { +			btw[btindex++] = E->key(); +			btw[btindex++] = compressed[E->get()].offset; +			btw[btindex++] = compressed[E->get()].compressed.size(); +			btw[btindex++] = compressed[E->get()].orig_len; +		} +	} + +	strings.resize(total_compression_size); +	uint8_t *cw = strings.ptrw(); + +	for (int i = 0; i < compressed.size(); i++) { +		memcpy(&cw[compressed[i].offset], compressed[i].compressed.get_data(), compressed[i].compressed.size()); +	} + +	ERR_FAIL_COND(btindex != bucket_table_size); +	set_locale(p_from->get_locale()); + +#endif +} + +bool PHashTranslation::_set(const StringName &p_name, const Variant &p_value) { +	String name = p_name.operator String(); +	if (name == "hash_table") { +		hash_table = p_value; +	} else if (name == "bucket_table") { +		bucket_table = p_value; +	} else if (name == "strings") { +		strings = p_value; +	} else if (name == "load_from") { +		generate(p_value); +	} else { +		return false; +	} + +	return true; +} + +bool PHashTranslation::_get(const StringName &p_name, Variant &r_ret) const { +	String name = p_name.operator String(); +	if (name == "hash_table") { +		r_ret = hash_table; +	} else if (name == "bucket_table") { +		r_ret = bucket_table; +	} else if (name == "strings") { +		r_ret = strings; +	} else { +		return false; +	} + +	return true; +} + +StringName PHashTranslation::get_message(const StringName &p_src_text, const StringName &p_context) const { +	// p_context passed in is ignore. The use of context is not yet supported in PHashTranslation. + +	int htsize = hash_table.size(); + +	if (htsize == 0) { +		return StringName(); +	} + +	CharString str = p_src_text.operator String().utf8(); +	uint32_t h = hash(0, str.get_data()); + +	const int *htr = hash_table.ptr(); +	const uint32_t *htptr = (const uint32_t *)&htr[0]; +	const int *btr = bucket_table.ptr(); +	const uint32_t *btptr = (const uint32_t *)&btr[0]; +	const uint8_t *sr = strings.ptr(); +	const char *sptr = (const char *)&sr[0]; + +	uint32_t p = htptr[h % htsize]; + +	if (p == 0xFFFFFFFF) { +		return StringName(); //nothing +	} + +	const Bucket &bucket = *(const Bucket *)&btptr[p]; + +	h = hash(bucket.func, str.get_data()); + +	int idx = -1; + +	for (int i = 0; i < bucket.size; i++) { +		if (bucket.elem[i].key == h) { +			idx = i; +			break; +		} +	} + +	if (idx == -1) { +		return StringName(); +	} + +	if (bucket.elem[idx].comp_size == bucket.elem[idx].uncomp_size) { +		String rstr; +		rstr.parse_utf8(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].uncomp_size); + +		return rstr; +	} else { +		CharString uncomp; +		uncomp.resize(bucket.elem[idx].uncomp_size + 1); +		smaz_decompress(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].comp_size, uncomp.ptrw(), bucket.elem[idx].uncomp_size); +		String rstr; +		rstr.parse_utf8(uncomp.get_data()); +		return rstr; +	} +} + +StringName PHashTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const { +	// The use of plurals translation is not yet supported in PHashTranslation. +	return get_message(p_src_text, p_context); +} + +void PHashTranslation::_get_property_list(List<PropertyInfo> *p_list) const { +	p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "hash_table")); +	p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "bucket_table")); +	p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "strings")); +	p_list->push_back(PropertyInfo(Variant::OBJECT, "load_from", PROPERTY_HINT_RESOURCE_TYPE, "Translation", PROPERTY_USAGE_EDITOR)); +} + +void PHashTranslation::_bind_methods() { +	ClassDB::bind_method(D_METHOD("generate", "from"), &PHashTranslation::generate); +} diff --git a/core/string/compressed_translation.h b/core/string/compressed_translation.h new file mode 100644 index 0000000000..efb3535362 --- /dev/null +++ b/core/string/compressed_translation.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/*  compressed_translation.h                                             */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef COMPRESSED_TRANSLATION_H +#define COMPRESSED_TRANSLATION_H + +#include "core/string/translation.h" + +class PHashTranslation : public Translation { +	GDCLASS(PHashTranslation, Translation); + +	//this translation uses a sort of modified perfect hash algorithm +	//it requires hashing strings twice and then does a binary search, +	//so it's slower, but at the same time it has an extreemly high chance +	//of catching untranslated strings + +	//load/store friendly types +	Vector<int> hash_table; +	Vector<int> bucket_table; +	Vector<uint8_t> strings; + +	struct Bucket { +		int size; +		uint32_t func; + +		struct Elem { +			uint32_t key; +			uint32_t str_offset; +			uint32_t comp_size; +			uint32_t uncomp_size; +		}; + +		Elem elem[1]; +	}; + +	_FORCE_INLINE_ uint32_t hash(uint32_t d, const char *p_str) const { +		if (d == 0) { +			d = 0x1000193; +		} +		while (*p_str) { +			d = (d * 0x1000193) ^ uint32_t(*p_str); +			p_str++; +		} + +		return d; +	} + +protected: +	bool _set(const StringName &p_name, const Variant &p_value); +	bool _get(const StringName &p_name, Variant &r_ret) const; +	void _get_property_list(List<PropertyInfo> *p_list) const; +	static void _bind_methods(); + +public: +	virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override; //overridable for other implementations +	virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override; +	void generate(const Ref<Translation> &p_from); + +	PHashTranslation() {} +}; + +#endif // COMPRESSED_TRANSLATION_H diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp new file mode 100644 index 0000000000..4d152d9258 --- /dev/null +++ b/core/string/node_path.cpp @@ -0,0 +1,429 @@ +/*************************************************************************/ +/*  node_path.cpp                                                        */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "node_path.h" + +#include "core/string/print_string.h" + +void NodePath::_update_hash_cache() const { +	uint32_t h = data->absolute ? 1 : 0; +	int pc = data->path.size(); +	const StringName *sn = data->path.ptr(); +	for (int i = 0; i < pc; i++) { +		h = h ^ sn[i].hash(); +	} +	int spc = data->subpath.size(); +	const StringName *ssn = data->subpath.ptr(); +	for (int i = 0; i < spc; i++) { +		h = h ^ ssn[i].hash(); +	} + +	data->hash_cache_valid = true; +	data->hash_cache = h; +} + +void NodePath::prepend_period() { +	if (data->path.size() && data->path[0].operator String() != ".") { +		data->path.insert(0, "."); +		data->hash_cache_valid = false; +	} +} + +bool NodePath::is_absolute() const { +	if (!data) { +		return false; +	} + +	return data->absolute; +} + +int NodePath::get_name_count() const { +	if (!data) { +		return 0; +	} + +	return data->path.size(); +} + +StringName NodePath::get_name(int p_idx) const { +	ERR_FAIL_COND_V(!data, StringName()); +	ERR_FAIL_INDEX_V(p_idx, data->path.size(), StringName()); +	return data->path[p_idx]; +} + +int NodePath::get_subname_count() const { +	if (!data) { +		return 0; +	} + +	return data->subpath.size(); +} + +StringName NodePath::get_subname(int p_idx) const { +	ERR_FAIL_COND_V(!data, StringName()); +	ERR_FAIL_INDEX_V(p_idx, data->subpath.size(), StringName()); +	return data->subpath[p_idx]; +} + +void NodePath::unref() { +	if (data && data->refcount.unref()) { +		memdelete(data); +	} +	data = nullptr; +} + +bool NodePath::operator==(const NodePath &p_path) const { +	if (data == p_path.data) { +		return true; +	} + +	if (!data || !p_path.data) { +		return false; +	} + +	if (data->absolute != p_path.data->absolute) { +		return false; +	} + +	int path_size = data->path.size(); + +	if (path_size != p_path.data->path.size()) { +		return false; +	} + +	int subpath_size = data->subpath.size(); + +	if (subpath_size != p_path.data->subpath.size()) { +		return false; +	} + +	const StringName *l_path_ptr = data->path.ptr(); +	const StringName *r_path_ptr = p_path.data->path.ptr(); + +	for (int i = 0; i < path_size; i++) { +		if (l_path_ptr[i] != r_path_ptr[i]) { +			return false; +		} +	} + +	const StringName *l_subpath_ptr = data->subpath.ptr(); +	const StringName *r_subpath_ptr = p_path.data->subpath.ptr(); + +	for (int i = 0; i < subpath_size; i++) { +		if (l_subpath_ptr[i] != r_subpath_ptr[i]) { +			return false; +		} +	} + +	return true; +} + +bool NodePath::operator!=(const NodePath &p_path) const { +	return (!(*this == p_path)); +} + +void NodePath::operator=(const NodePath &p_path) { +	if (this == &p_path) { +		return; +	} + +	unref(); + +	if (p_path.data && p_path.data->refcount.ref()) { +		data = p_path.data; +	} +} + +NodePath::operator String() const { +	if (!data) { +		return String(); +	} + +	String ret; +	if (data->absolute) { +		ret = "/"; +	} + +	for (int i = 0; i < data->path.size(); i++) { +		if (i > 0) { +			ret += "/"; +		} +		ret += data->path[i].operator String(); +	} + +	for (int i = 0; i < data->subpath.size(); i++) { +		ret += ":" + data->subpath[i].operator String(); +	} + +	return ret; +} + +Vector<StringName> NodePath::get_names() const { +	if (data) { +		return data->path; +	} +	return Vector<StringName>(); +} + +Vector<StringName> NodePath::get_subnames() const { +	if (data) { +		return data->subpath; +	} +	return Vector<StringName>(); +} + +StringName NodePath::get_concatenated_subnames() const { +	ERR_FAIL_COND_V(!data, StringName()); + +	if (!data->concatenated_subpath) { +		int spc = data->subpath.size(); +		String concatenated; +		const StringName *ssn = data->subpath.ptr(); +		for (int i = 0; i < spc; i++) { +			concatenated += i == 0 ? ssn[i].operator String() : ":" + ssn[i]; +		} +		data->concatenated_subpath = concatenated; +	} +	return data->concatenated_subpath; +} + +NodePath NodePath::rel_path_to(const NodePath &p_np) const { +	ERR_FAIL_COND_V(!is_absolute(), NodePath()); +	ERR_FAIL_COND_V(!p_np.is_absolute(), NodePath()); + +	Vector<StringName> src_dirs = get_names(); +	Vector<StringName> dst_dirs = p_np.get_names(); + +	//find common parent +	int common_parent = 0; + +	while (true) { +		if (src_dirs.size() == common_parent) { +			break; +		} +		if (dst_dirs.size() == common_parent) { +			break; +		} +		if (src_dirs[common_parent] != dst_dirs[common_parent]) { +			break; +		} +		common_parent++; +	} + +	common_parent--; + +	Vector<StringName> relpath; + +	for (int i = src_dirs.size() - 1; i > common_parent; i--) { +		relpath.push_back(".."); +	} + +	for (int i = common_parent + 1; i < dst_dirs.size(); i++) { +		relpath.push_back(dst_dirs[i]); +	} + +	if (relpath.size() == 0) { +		relpath.push_back("."); +	} + +	return NodePath(relpath, p_np.get_subnames(), false); +} + +NodePath NodePath::get_as_property_path() const { +	if (!data || !data->path.size()) { +		return *this; +	} else { +		Vector<StringName> new_path = data->subpath; + +		String initial_subname = data->path[0]; + +		for (int i = 1; i < data->path.size(); i++) { +			initial_subname += "/" + data->path[i]; +		} +		new_path.insert(0, initial_subname); + +		return NodePath(Vector<StringName>(), new_path, false); +	} +} + +bool NodePath::is_empty() const { +	return !data; +} + +void NodePath::simplify() { +	if (!data) { +		return; +	} +	for (int i = 0; i < data->path.size(); i++) { +		if (data->path.size() == 1) { +			break; +		} +		if (data->path[i].operator String() == ".") { +			data->path.remove(i); +			i--; +		} else if (data->path[i].operator String() == ".." && i > 0 && data->path[i - 1].operator String() != "." && data->path[i - 1].operator String() != "..") { +			//remove both +			data->path.remove(i - 1); +			data->path.remove(i - 1); +			i -= 2; +			if (data->path.size() == 0) { +				data->path.push_back("."); +				break; +			} +		} +	} +	data->hash_cache_valid = false; +} + +NodePath NodePath::simplified() const { +	NodePath np = *this; +	np.simplify(); +	return np; +} + +NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) { +	if (p_path.size() == 0) { +		return; +	} + +	data = memnew(Data); +	data->refcount.init(); +	data->absolute = p_absolute; +	data->path = p_path; +	data->has_slashes = true; +	data->hash_cache_valid = false; +} + +NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) { +	if (p_path.size() == 0 && p_subpath.size() == 0) { +		return; +	} + +	data = memnew(Data); +	data->refcount.init(); +	data->absolute = p_absolute; +	data->path = p_path; +	data->subpath = p_subpath; +	data->has_slashes = true; +	data->hash_cache_valid = false; +} + +NodePath::NodePath(const NodePath &p_path) { +	if (p_path.data && p_path.data->refcount.ref()) { +		data = p_path.data; +	} +} + +NodePath::NodePath(const String &p_path) { +	if (p_path.length() == 0) { +		return; +	} + +	String path = p_path; +	Vector<StringName> subpath; + +	bool absolute = (path[0] == '/'); +	bool last_is_slash = true; +	bool has_slashes = false; +	int slices = 0; +	int subpath_pos = path.find(":"); + +	if (subpath_pos != -1) { +		int from = subpath_pos + 1; + +		for (int i = from; i <= path.length(); i++) { +			if (path[i] == ':' || path[i] == 0) { +				String str = path.substr(from, i - from); +				if (str == "") { +					if (path[i] == 0) { +						continue; // Allow end-of-path : +					} + +					ERR_FAIL_MSG("Invalid NodePath '" + p_path + "'."); +				} +				subpath.push_back(str); + +				from = i + 1; +			} +		} + +		path = path.substr(0, subpath_pos); +	} + +	for (int i = (int)absolute; i < path.length(); i++) { +		if (path[i] == '/') { +			last_is_slash = true; +			has_slashes = true; +		} else { +			if (last_is_slash) { +				slices++; +			} + +			last_is_slash = false; +		} +	} + +	if (slices == 0 && !absolute && !subpath.size()) { +		return; +	} + +	data = memnew(Data); +	data->refcount.init(); +	data->absolute = absolute; +	data->has_slashes = has_slashes; +	data->subpath = subpath; +	data->hash_cache_valid = false; + +	if (slices == 0) { +		return; +	} +	data->path.resize(slices); +	last_is_slash = true; +	int from = (int)absolute; +	int slice = 0; + +	for (int i = (int)absolute; i < path.length() + 1; i++) { +		if (path[i] == '/' || path[i] == 0) { +			if (!last_is_slash) { +				String name = path.substr(from, i - from); +				ERR_FAIL_INDEX(slice, data->path.size()); +				data->path.write[slice++] = name; +			} +			from = i + 1; +			last_is_slash = true; +		} else { +			last_is_slash = false; +		} +	} +} + +NodePath::~NodePath() { +	unref(); +} diff --git a/core/string/node_path.h b/core/string/node_path.h new file mode 100644 index 0000000000..b4513ddb3c --- /dev/null +++ b/core/string/node_path.h @@ -0,0 +1,99 @@ +/*************************************************************************/ +/*  node_path.h                                                          */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef NODE_PATH_H +#define NODE_PATH_H + +#include "core/string/string_name.h" +#include "core/string/ustring.h" + +class NodePath { +	struct Data { +		SafeRefCount refcount; +		Vector<StringName> path; +		Vector<StringName> subpath; +		StringName concatenated_subpath; +		bool absolute; +		bool has_slashes; +		mutable bool hash_cache_valid; +		mutable uint32_t hash_cache; +	}; + +	mutable Data *data = nullptr; +	void unref(); + +	void _update_hash_cache() const; + +public: +	bool is_absolute() const; +	int get_name_count() const; +	StringName get_name(int p_idx) const; +	int get_subname_count() const; +	StringName get_subname(int p_idx) const; +	Vector<StringName> get_names() const; +	Vector<StringName> get_subnames() const; +	StringName get_concatenated_subnames() const; + +	NodePath rel_path_to(const NodePath &p_np) const; +	NodePath get_as_property_path() const; + +	void prepend_period(); + +	NodePath get_parent() const; + +	_FORCE_INLINE_ uint32_t hash() const { +		if (!data) { +			return 0; +		} +		if (!data->hash_cache_valid) { +			_update_hash_cache(); +		} +		return data->hash_cache; +	} + +	operator String() const; +	bool is_empty() const; + +	bool operator==(const NodePath &p_path) const; +	bool operator!=(const NodePath &p_path) const; +	void operator=(const NodePath &p_path); + +	void simplify(); +	NodePath simplified() const; + +	NodePath(const Vector<StringName> &p_path, bool p_absolute); +	NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute); +	NodePath(const NodePath &p_path); +	NodePath(const String &p_path); +	NodePath() {} +	~NodePath(); +}; + +#endif // NODE_PATH_H diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp new file mode 100644 index 0000000000..54de229471 --- /dev/null +++ b/core/string/print_string.cpp @@ -0,0 +1,110 @@ +/*************************************************************************/ +/*  print_string.cpp                                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "print_string.h" + +#include "core/os/os.h" + +#include <stdio.h> + +static PrintHandlerList *print_handler_list = nullptr; +bool _print_line_enabled = true; +bool _print_error_enabled = true; + +void add_print_handler(PrintHandlerList *p_handler) { +	_global_lock(); +	p_handler->next = print_handler_list; +	print_handler_list = p_handler; +	_global_unlock(); +} + +void remove_print_handler(PrintHandlerList *p_handler) { +	_global_lock(); + +	PrintHandlerList *prev = nullptr; +	PrintHandlerList *l = print_handler_list; + +	while (l) { +		if (l == p_handler) { +			if (prev) { +				prev->next = l->next; +			} else { +				print_handler_list = l->next; +			} +			break; +		} +		prev = l; +		l = l->next; +	} +	//OS::get_singleton()->print("print handler list is %p\n",print_handler_list); + +	_global_unlock(); +	ERR_FAIL_COND(l == nullptr); +} + +void print_line(String p_string) { +	if (!_print_line_enabled) { +		return; +	} + +	OS::get_singleton()->print("%s\n", p_string.utf8().get_data()); + +	_global_lock(); +	PrintHandlerList *l = print_handler_list; +	while (l) { +		l->printfunc(l->userdata, p_string, false); +		l = l->next; +	} + +	_global_unlock(); +} + +void print_error(String p_string) { +	if (!_print_error_enabled) { +		return; +	} + +	OS::get_singleton()->printerr("%s\n", p_string.utf8().get_data()); + +	_global_lock(); +	PrintHandlerList *l = print_handler_list; +	while (l) { +		l->printfunc(l->userdata, p_string, true); +		l = l->next; +	} + +	_global_unlock(); +} + +void print_verbose(String p_string) { +	if (OS::get_singleton()->is_stdout_verbose()) { +		print_line(p_string); +	} +} diff --git a/core/string/print_string.h b/core/string/print_string.h new file mode 100644 index 0000000000..3e8f244cc5 --- /dev/null +++ b/core/string/print_string.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/*  print_string.h                                                       */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef PRINT_STRING_H +#define PRINT_STRING_H + +#include "core/string/ustring.h" + +extern void (*_print_func)(String); + +typedef void (*PrintHandlerFunc)(void *, const String &p_string, bool p_error); + +struct PrintHandlerList { +	PrintHandlerFunc printfunc = nullptr; +	void *userdata = nullptr; + +	PrintHandlerList *next = nullptr; + +	PrintHandlerList() {} +}; + +void add_print_handler(PrintHandlerList *p_handler); +void remove_print_handler(PrintHandlerList *p_handler); + +extern bool _print_line_enabled; +extern bool _print_error_enabled; +extern void print_line(String p_string); +extern void print_error(String p_string); +extern void print_verbose(String p_string); + +#endif // PRINT_STRING_H diff --git a/core/string/string_buffer.h b/core/string/string_buffer.h new file mode 100644 index 0000000000..1317b538d4 --- /dev/null +++ b/core/string/string_buffer.h @@ -0,0 +1,162 @@ +/*************************************************************************/ +/*  string_buffer.h                                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef STRING_BUFFER_H +#define STRING_BUFFER_H + +#include "core/string/ustring.h" + +template <int SHORT_BUFFER_SIZE = 64> +class StringBuffer { +	char32_t short_buffer[SHORT_BUFFER_SIZE]; +	String buffer; +	int string_length = 0; + +	_FORCE_INLINE_ char32_t *current_buffer_ptr() { +		return static_cast<String &>(buffer).empty() ? short_buffer : buffer.ptrw(); +	} + +public: +	StringBuffer &append(char32_t p_char); +	StringBuffer &append(const String &p_string); +	StringBuffer &append(const char *p_str); +	StringBuffer &append(const char32_t *p_str, int p_clip_to_len = -1); + +	_FORCE_INLINE_ void operator+=(char32_t p_char) { +		append(p_char); +	} + +	_FORCE_INLINE_ void operator+=(const String &p_string) { +		append(p_string); +	} + +	_FORCE_INLINE_ void operator+=(const char *p_str) { +		append(p_str); +	} + +	_FORCE_INLINE_ void operator+=(const char32_t *p_str) { +		append(p_str); +	} + +	StringBuffer &reserve(int p_size); + +	int length() const; + +	String as_string(); + +	double as_double(); +	int64_t as_int(); + +	_FORCE_INLINE_ operator String() { +		return as_string(); +	} +}; + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(char32_t p_char) { +	reserve(string_length + 2); +	current_buffer_ptr()[string_length++] = p_char; +	return *this; +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const String &p_string) { +	return append(p_string.get_data()); +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const char *p_str) { +	int len = strlen(p_str); +	reserve(string_length + len + 1); + +	char32_t *buf = current_buffer_ptr(); +	for (const char *c_ptr = p_str; *c_ptr; ++c_ptr) { +		buf[string_length++] = *c_ptr; +	} +	return *this; +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const char32_t *p_str, int p_clip_to_len) { +	int len = 0; +	while ((p_clip_to_len < 0 || len < p_clip_to_len) && p_str[len]) { +		++len; +	} +	reserve(string_length + len + 1); +	memcpy(&(current_buffer_ptr()[string_length]), p_str, len * sizeof(char32_t)); +	string_length += len; + +	return *this; +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::reserve(int p_size) { +	if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size()) { +		return *this; +	} + +	bool need_copy = string_length > 0 && buffer.empty(); +	buffer.resize(next_power_of_2(p_size)); +	if (need_copy) { +		memcpy(buffer.ptrw(), short_buffer, string_length * sizeof(char32_t)); +	} + +	return *this; +} + +template <int SHORT_BUFFER_SIZE> +int StringBuffer<SHORT_BUFFER_SIZE>::length() const { +	return string_length; +} + +template <int SHORT_BUFFER_SIZE> +String StringBuffer<SHORT_BUFFER_SIZE>::as_string() { +	current_buffer_ptr()[string_length] = '\0'; +	if (buffer.empty()) { +		return String(short_buffer); +	} else { +		buffer.resize(string_length + 1); +		return buffer; +	} +} + +template <int SHORT_BUFFER_SIZE> +double StringBuffer<SHORT_BUFFER_SIZE>::as_double() { +	current_buffer_ptr()[string_length] = '\0'; +	return String::to_float(current_buffer_ptr()); +} + +template <int SHORT_BUFFER_SIZE> +int64_t StringBuffer<SHORT_BUFFER_SIZE>::as_int() { +	current_buffer_ptr()[string_length] = '\0'; +	return String::to_int(current_buffer_ptr()); +} + +#endif // STRING_BUFFER_H diff --git a/core/string/string_builder.cpp b/core/string/string_builder.cpp new file mode 100644 index 0000000000..dec299ffa3 --- /dev/null +++ b/core/string/string_builder.cpp @@ -0,0 +1,99 @@ +/*************************************************************************/ +/*  string_builder.cpp                                                   */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "string_builder.h" + +#include <string.h> + +StringBuilder &StringBuilder::append(const String &p_string) { +	if (p_string == String()) { +		return *this; +	} + +	strings.push_back(p_string); +	appended_strings.push_back(-1); + +	string_length += p_string.length(); + +	return *this; +} + +StringBuilder &StringBuilder::append(const char *p_cstring) { +	int32_t len = strlen(p_cstring); + +	c_strings.push_back(p_cstring); +	appended_strings.push_back(len); + +	string_length += len; + +	return *this; +} + +String StringBuilder::as_string() const { +	if (string_length == 0) { +		return ""; +	} + +	char32_t *buffer = memnew_arr(char32_t, string_length); + +	int current_position = 0; + +	int godot_string_elem = 0; +	int c_string_elem = 0; + +	for (int i = 0; i < appended_strings.size(); i++) { +		if (appended_strings[i] == -1) { +			// Godot string +			const String &s = strings[godot_string_elem]; + +			memcpy(buffer + current_position, s.ptr(), s.length() * sizeof(char32_t)); + +			current_position += s.length(); + +			godot_string_elem++; +		} else { +			const char *s = c_strings[c_string_elem]; + +			for (int32_t j = 0; j < appended_strings[i]; j++) { +				buffer[current_position + j] = s[j]; +			} + +			current_position += appended_strings[i]; + +			c_string_elem++; +		} +	} + +	String final_string = String(buffer, string_length); + +	memdelete_arr(buffer); + +	return final_string; +} diff --git a/core/string/string_builder.h b/core/string/string_builder.h new file mode 100644 index 0000000000..c732f1b9ea --- /dev/null +++ b/core/string/string_builder.h @@ -0,0 +1,84 @@ +/*************************************************************************/ +/*  string_builder.h                                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef STRING_BUILDER_H +#define STRING_BUILDER_H + +#include "core/string/ustring.h" +#include "core/templates/vector.h" + +class StringBuilder { +	uint32_t string_length = 0; + +	Vector<String> strings; +	Vector<const char *> c_strings; + +	// -1 means it's a Godot String +	// a natural number means C string. +	Vector<int32_t> appended_strings; + +public: +	StringBuilder &append(const String &p_string); +	StringBuilder &append(const char *p_cstring); + +	_FORCE_INLINE_ StringBuilder &operator+(const String &p_string) { +		return append(p_string); +	} + +	_FORCE_INLINE_ StringBuilder &operator+(const char *p_cstring) { +		return append(p_cstring); +	} + +	_FORCE_INLINE_ void operator+=(const String &p_string) { +		append(p_string); +	} + +	_FORCE_INLINE_ void operator+=(const char *p_cstring) { +		append(p_cstring); +	} + +	_FORCE_INLINE_ int num_strings_appended() const { +		return appended_strings.size(); +	} + +	_FORCE_INLINE_ uint32_t get_string_length() const { +		return string_length; +	} + +	String as_string() const; + +	_FORCE_INLINE_ operator String() const { +		return as_string(); +	} + +	StringBuilder() {} +}; + +#endif // STRING_BUILDER_H diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp new file mode 100644 index 0000000000..34afdaee38 --- /dev/null +++ b/core/string/string_name.cpp @@ -0,0 +1,393 @@ +/*************************************************************************/ +/*  string_name.cpp                                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "string_name.h" + +#include "core/os/os.h" +#include "core/string/print_string.h" + +StaticCString StaticCString::create(const char *p_ptr) { +	StaticCString scs; +	scs.ptr = p_ptr; +	return scs; +} + +StringName::_Data *StringName::_table[STRING_TABLE_LEN]; + +StringName _scs_create(const char *p_chr) { +	return (p_chr[0] ? StringName(StaticCString::create(p_chr)) : StringName()); +} + +bool StringName::configured = false; +Mutex StringName::mutex; + +void StringName::setup() { +	ERR_FAIL_COND(configured); +	for (int i = 0; i < STRING_TABLE_LEN; i++) { +		_table[i] = nullptr; +	} +	configured = true; +} + +void StringName::cleanup() { +	MutexLock lock(mutex); + +	int lost_strings = 0; +	for (int i = 0; i < STRING_TABLE_LEN; i++) { +		while (_table[i]) { +			_Data *d = _table[i]; +			lost_strings++; +			if (OS::get_singleton()->is_stdout_verbose()) { +				if (d->cname) { +					print_line("Orphan StringName: " + String(d->cname)); +				} else { +					print_line("Orphan StringName: " + String(d->name)); +				} +			} + +			_table[i] = _table[i]->next; +			memdelete(d); +		} +	} +	if (lost_strings) { +		print_verbose("StringName: " + itos(lost_strings) + " unclaimed string names at exit."); +	} +} + +void StringName::unref() { +	ERR_FAIL_COND(!configured); + +	if (_data && _data->refcount.unref()) { +		MutexLock lock(mutex); + +		if (_data->prev) { +			_data->prev->next = _data->next; +		} else { +			if (_table[_data->idx] != _data) { +				ERR_PRINT("BUG!"); +			} +			_table[_data->idx] = _data->next; +		} + +		if (_data->next) { +			_data->next->prev = _data->prev; +		} +		memdelete(_data); +	} + +	_data = nullptr; +} + +bool StringName::operator==(const String &p_name) const { +	if (!_data) { +		return (p_name.length() == 0); +	} + +	return (_data->get_name() == p_name); +} + +bool StringName::operator==(const char *p_name) const { +	if (!_data) { +		return (p_name[0] == 0); +	} + +	return (_data->get_name() == p_name); +} + +bool StringName::operator!=(const String &p_name) const { +	return !(operator==(p_name)); +} + +bool StringName::operator!=(const StringName &p_name) const { +	// the real magic of all this mess happens here. +	// this is why path comparisons are very fast +	return _data != p_name._data; +} + +void StringName::operator=(const StringName &p_name) { +	if (this == &p_name) { +		return; +	} + +	unref(); + +	if (p_name._data && p_name._data->refcount.ref()) { +		_data = p_name._data; +	} +} + +StringName::StringName(const StringName &p_name) { +	_data = nullptr; + +	ERR_FAIL_COND(!configured); + +	if (p_name._data && p_name._data->refcount.ref()) { +		_data = p_name._data; +	} +} + +StringName::StringName(const char *p_name) { +	_data = nullptr; + +	ERR_FAIL_COND(!configured); + +	if (!p_name || p_name[0] == 0) { +		return; //empty, ignore +	} + +	MutexLock lock(mutex); + +	uint32_t hash = String::hash(p_name); + +	uint32_t idx = hash & STRING_TABLE_MASK; + +	_data = _table[idx]; + +	while (_data) { +		// compare hash first +		if (_data->hash == hash && _data->get_name() == p_name) { +			break; +		} +		_data = _data->next; +	} + +	if (_data) { +		if (_data->refcount.ref()) { +			// exists +			return; +		} +	} + +	_data = memnew(_Data); +	_data->name = p_name; +	_data->refcount.init(); +	_data->hash = hash; +	_data->idx = idx; +	_data->cname = nullptr; +	_data->next = _table[idx]; +	_data->prev = nullptr; +	if (_table[idx]) { +		_table[idx]->prev = _data; +	} +	_table[idx] = _data; +} + +StringName::StringName(const StaticCString &p_static_string) { +	_data = nullptr; + +	ERR_FAIL_COND(!configured); + +	ERR_FAIL_COND(!p_static_string.ptr || !p_static_string.ptr[0]); + +	MutexLock lock(mutex); + +	uint32_t hash = String::hash(p_static_string.ptr); + +	uint32_t idx = hash & STRING_TABLE_MASK; + +	_data = _table[idx]; + +	while (_data) { +		// compare hash first +		if (_data->hash == hash && _data->get_name() == p_static_string.ptr) { +			break; +		} +		_data = _data->next; +	} + +	if (_data) { +		if (_data->refcount.ref()) { +			// exists +			return; +		} +	} + +	_data = memnew(_Data); + +	_data->refcount.init(); +	_data->hash = hash; +	_data->idx = idx; +	_data->cname = p_static_string.ptr; +	_data->next = _table[idx]; +	_data->prev = nullptr; +	if (_table[idx]) { +		_table[idx]->prev = _data; +	} +	_table[idx] = _data; +} + +StringName::StringName(const String &p_name) { +	_data = nullptr; + +	ERR_FAIL_COND(!configured); + +	if (p_name == String()) { +		return; +	} + +	MutexLock lock(mutex); + +	uint32_t hash = p_name.hash(); +	uint32_t idx = hash & STRING_TABLE_MASK; + +	_data = _table[idx]; + +	while (_data) { +		if (_data->hash == hash && _data->get_name() == p_name) { +			break; +		} +		_data = _data->next; +	} + +	if (_data) { +		if (_data->refcount.ref()) { +			// exists +			return; +		} +	} + +	_data = memnew(_Data); +	_data->name = p_name; +	_data->refcount.init(); +	_data->hash = hash; +	_data->idx = idx; +	_data->cname = nullptr; +	_data->next = _table[idx]; +	_data->prev = nullptr; +	if (_table[idx]) { +		_table[idx]->prev = _data; +	} +	_table[idx] = _data; +} + +StringName StringName::search(const char *p_name) { +	ERR_FAIL_COND_V(!configured, StringName()); + +	ERR_FAIL_COND_V(!p_name, StringName()); +	if (!p_name[0]) { +		return StringName(); +	} + +	MutexLock lock(mutex); + +	uint32_t hash = String::hash(p_name); +	uint32_t idx = hash & STRING_TABLE_MASK; + +	_Data *_data = _table[idx]; + +	while (_data) { +		// compare hash first +		if (_data->hash == hash && _data->get_name() == p_name) { +			break; +		} +		_data = _data->next; +	} + +	if (_data && _data->refcount.ref()) { +		return StringName(_data); +	} + +	return StringName(); //does not exist +} + +StringName StringName::search(const char32_t *p_name) { +	ERR_FAIL_COND_V(!configured, StringName()); + +	ERR_FAIL_COND_V(!p_name, StringName()); +	if (!p_name[0]) { +		return StringName(); +	} + +	MutexLock lock(mutex); + +	uint32_t hash = String::hash(p_name); + +	uint32_t idx = hash & STRING_TABLE_MASK; + +	_Data *_data = _table[idx]; + +	while (_data) { +		// compare hash first +		if (_data->hash == hash && _data->get_name() == p_name) { +			break; +		} +		_data = _data->next; +	} + +	if (_data && _data->refcount.ref()) { +		return StringName(_data); +	} + +	return StringName(); //does not exist +} + +StringName StringName::search(const String &p_name) { +	ERR_FAIL_COND_V(p_name == "", StringName()); + +	MutexLock lock(mutex); + +	uint32_t hash = p_name.hash(); + +	uint32_t idx = hash & STRING_TABLE_MASK; + +	_Data *_data = _table[idx]; + +	while (_data) { +		// compare hash first +		if (_data->hash == hash && p_name == _data->get_name()) { +			break; +		} +		_data = _data->next; +	} + +	if (_data && _data->refcount.ref()) { +		return StringName(_data); +	} + +	return StringName(); //does not exist +} + +StringName::~StringName() { +	unref(); +} + +bool operator==(const String &p_name, const StringName &p_string_name) { +	return p_name == p_string_name.operator String(); +} +bool operator!=(const String &p_name, const StringName &p_string_name) { +	return p_name != p_string_name.operator String(); +} + +bool operator==(const char *p_name, const StringName &p_string_name) { +	return p_name == p_string_name.operator String(); +} +bool operator!=(const char *p_name, const StringName &p_string_name) { +	return p_name != p_string_name.operator String(); +} diff --git a/core/string/string_name.h b/core/string/string_name.h new file mode 100644 index 0000000000..320f63bf68 --- /dev/null +++ b/core/string/string_name.h @@ -0,0 +1,164 @@ +/*************************************************************************/ +/*  string_name.h                                                        */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef STRING_NAME_H +#define STRING_NAME_H + +#include "core/os/mutex.h" +#include "core/string/ustring.h" +#include "core/templates/safe_refcount.h" + +class Main; + +struct StaticCString { +	const char *ptr; +	static StaticCString create(const char *p_ptr); +}; + +class StringName { +	enum { +		STRING_TABLE_BITS = 12, +		STRING_TABLE_LEN = 1 << STRING_TABLE_BITS, +		STRING_TABLE_MASK = STRING_TABLE_LEN - 1 +	}; + +	struct _Data { +		SafeRefCount refcount; +		const char *cname = nullptr; +		String name; + +		String get_name() const { return cname ? String(cname) : name; } +		int idx = 0; +		uint32_t hash = 0; +		_Data *prev = nullptr; +		_Data *next = nullptr; +		_Data() {} +	}; + +	static _Data *_table[STRING_TABLE_LEN]; + +	_Data *_data = nullptr; + +	union _HashUnion { +		_Data *ptr; +		uint32_t hash; +	}; + +	void unref(); +	friend void register_core_types(); +	friend void unregister_core_types(); +	friend class Main; +	static Mutex mutex; +	static void setup(); +	static void cleanup(); +	static bool configured; + +	StringName(_Data *p_data) { _data = p_data; } + +public: +	operator const void *() const { return (_data && (_data->cname || !_data->name.empty())) ? (void *)1 : nullptr; } + +	bool operator==(const String &p_name) const; +	bool operator==(const char *p_name) const; +	bool operator!=(const String &p_name) const; +	_FORCE_INLINE_ bool operator<(const StringName &p_name) const { +		return _data < p_name._data; +	} +	_FORCE_INLINE_ bool operator==(const StringName &p_name) const { +		// the real magic of all this mess happens here. +		// this is why path comparisons are very fast +		return _data == p_name._data; +	} +	_FORCE_INLINE_ uint32_t hash() const { +		if (_data) { +			return _data->hash; +		} else { +			return 0; +		} +	} +	_FORCE_INLINE_ const void *data_unique_pointer() const { +		return (void *)_data; +	} +	bool operator!=(const StringName &p_name) const; + +	_FORCE_INLINE_ operator String() const { +		if (_data) { +			if (_data->cname) { +				return String(_data->cname); +			} else { +				return _data->name; +			} +		} + +		return String(); +	} + +	static StringName search(const char *p_name); +	static StringName search(const char32_t *p_name); +	static StringName search(const String &p_name); + +	struct AlphCompare { +		_FORCE_INLINE_ bool operator()(const StringName &l, const StringName &r) const { +			const char *l_cname = l._data ? l._data->cname : ""; +			const char *r_cname = r._data ? r._data->cname : ""; + +			if (l_cname) { +				if (r_cname) { +					return is_str_less(l_cname, r_cname); +				} else { +					return is_str_less(l_cname, r._data->name.ptr()); +				} +			} else { +				if (r_cname) { +					return is_str_less(l._data->name.ptr(), r_cname); +				} else { +					return is_str_less(l._data->name.ptr(), r._data->name.ptr()); +				} +			} +		} +	}; + +	void operator=(const StringName &p_name); +	StringName(const char *p_name); +	StringName(const StringName &p_name); +	StringName(const String &p_name); +	StringName(const StaticCString &p_static_string); +	StringName() {} +	~StringName(); +}; + +bool operator==(const String &p_name, const StringName &p_string_name); +bool operator!=(const String &p_name, const StringName &p_string_name); +bool operator==(const char *p_name, const StringName &p_string_name); +bool operator!=(const char *p_name, const StringName &p_string_name); + +StringName _scs_create(const char *p_chr); + +#endif // STRING_NAME_H diff --git a/core/string/translation.cpp b/core/string/translation.cpp new file mode 100644 index 0000000000..df8a26e5ce --- /dev/null +++ b/core/string/translation.cpp @@ -0,0 +1,1303 @@ +/*************************************************************************/ +/*  translation.cpp                                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "translation.h" + +#include "core/config/project_settings.h" +#include "core/io/resource_loader.h" +#include "core/os/os.h" + +// ISO 639-1 language codes, with the addition of glibc locales with their +// regional identifiers. This list must match the language names (in English) +// of locale_names. +// +// References: +// - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +// - https://lh.2xlibre.net/locales/ + +static const char *locale_list[] = { +	"aa", //  Afar +	"aa_DJ", //  Afar (Djibouti) +	"aa_ER", //  Afar (Eritrea) +	"aa_ET", //  Afar (Ethiopia) +	"af", //  Afrikaans +	"af_ZA", //  Afrikaans (South Africa) +	"agr_PE", //  Aguaruna (Peru) +	"ak_GH", //  Akan (Ghana) +	"am_ET", //  Amharic (Ethiopia) +	"an_ES", //  Aragonese (Spain) +	"anp_IN", //  Angika (India) +	"ar", //  Arabic +	"ar_AE", //  Arabic (United Arab Emirates) +	"ar_BH", //  Arabic (Bahrain) +	"ar_DZ", //  Arabic (Algeria) +	"ar_EG", //  Arabic (Egypt) +	"ar_IN", //  Arabic (India) +	"ar_IQ", //  Arabic (Iraq) +	"ar_JO", //  Arabic (Jordan) +	"ar_KW", //  Arabic (Kuwait) +	"ar_LB", //  Arabic (Lebanon) +	"ar_LY", //  Arabic (Libya) +	"ar_MA", //  Arabic (Morocco) +	"ar_OM", //  Arabic (Oman) +	"ar_QA", //  Arabic (Qatar) +	"ar_SA", //  Arabic (Saudi Arabia) +	"ar_SD", //  Arabic (Sudan) +	"ar_SS", //  Arabic (South Soudan) +	"ar_SY", //  Arabic (Syria) +	"ar_TN", //  Arabic (Tunisia) +	"ar_YE", //  Arabic (Yemen) +	"as_IN", //  Assamese (India) +	"ast_ES", //  Asturian (Spain) +	"ayc_PE", //  Southern Aymara (Peru) +	"ay_PE", //  Aymara (Peru) +	"az_AZ", //  Azerbaijani (Azerbaijan) +	"be", //  Belarusian +	"be_BY", //  Belarusian (Belarus) +	"bem_ZM", //  Bemba (Zambia) +	"ber_DZ", //  Berber languages (Algeria) +	"ber_MA", //  Berber languages (Morocco) +	"bg", //  Bulgarian +	"bg_BG", //  Bulgarian (Bulgaria) +	"bhb_IN", //  Bhili (India) +	"bho_IN", //  Bhojpuri (India) +	"bi_TV", //  Bislama (Tuvalu) +	"bn", //  Bengali +	"bn_BD", //  Bengali (Bangladesh) +	"bn_IN", //  Bengali (India) +	"bo", //  Tibetan +	"bo_CN", //  Tibetan (China) +	"bo_IN", //  Tibetan (India) +	"br_FR", //  Breton (France) +	"brx_IN", //  Bodo (India) +	"bs_BA", //  Bosnian (Bosnia and Herzegovina) +	"byn_ER", //  Bilin (Eritrea) +	"ca", //  Catalan +	"ca_AD", //  Catalan (Andorra) +	"ca_ES", //  Catalan (Spain) +	"ca_FR", //  Catalan (France) +	"ca_IT", //  Catalan (Italy) +	"ce_RU", //  Chechen (Russia) +	"chr_US", //  Cherokee (United States) +	"cmn_TW", //  Mandarin Chinese (Taiwan) +	"crh_UA", //  Crimean Tatar (Ukraine) +	"csb_PL", //  Kashubian (Poland) +	"cs", //  Czech +	"cs_CZ", //  Czech (Czech Republic) +	"cv_RU", //  Chuvash (Russia) +	"cy_GB", //  Welsh (United Kingdom) +	"da", //  Danish +	"da_DK", //  Danish (Denmark) +	"de", //  German +	"de_AT", //  German (Austria) +	"de_BE", //  German (Belgium) +	"de_CH", //  German (Switzerland) +	"de_DE", //  German (Germany) +	"de_IT", //  German (Italy) +	"de_LU", //  German (Luxembourg) +	"doi_IN", //  Dogri (India) +	"dv_MV", //  Dhivehi (Maldives) +	"dz_BT", //  Dzongkha (Bhutan) +	"el", //  Greek +	"el_CY", //  Greek (Cyprus) +	"el_GR", //  Greek (Greece) +	"en", //  English +	"en_AG", //  English (Antigua and Barbuda) +	"en_AU", //  English (Australia) +	"en_BW", //  English (Botswana) +	"en_CA", //  English (Canada) +	"en_DK", //  English (Denmark) +	"en_GB", //  English (United Kingdom) +	"en_HK", //  English (Hong Kong) +	"en_IE", //  English (Ireland) +	"en_IL", //  English (Israel) +	"en_IN", //  English (India) +	"en_NG", //  English (Nigeria) +	"en_NZ", //  English (New Zealand) +	"en_PH", //  English (Philippines) +	"en_SG", //  English (Singapore) +	"en_US", //  English (United States) +	"en_ZA", //  English (South Africa) +	"en_ZM", //  English (Zambia) +	"en_ZW", //  English (Zimbabwe) +	"eo", //  Esperanto +	"es", //  Spanish +	"es_AR", //  Spanish (Argentina) +	"es_BO", //  Spanish (Bolivia) +	"es_CL", //  Spanish (Chile) +	"es_CO", //  Spanish (Colombia) +	"es_CR", //  Spanish (Costa Rica) +	"es_CU", //  Spanish (Cuba) +	"es_DO", //  Spanish (Dominican Republic) +	"es_EC", //  Spanish (Ecuador) +	"es_ES", //  Spanish (Spain) +	"es_GT", //  Spanish (Guatemala) +	"es_HN", //  Spanish (Honduras) +	"es_MX", //  Spanish (Mexico) +	"es_NI", //  Spanish (Nicaragua) +	"es_PA", //  Spanish (Panama) +	"es_PE", //  Spanish (Peru) +	"es_PR", //  Spanish (Puerto Rico) +	"es_PY", //  Spanish (Paraguay) +	"es_SV", //  Spanish (El Salvador) +	"es_US", //  Spanish (United States) +	"es_UY", //  Spanish (Uruguay) +	"es_VE", //  Spanish (Venezuela) +	"et", //  Estonian +	"et_EE", //  Estonian (Estonia) +	"eu", //  Basque +	"eu_ES", //  Basque (Spain) +	"fa", //  Persian +	"fa_IR", //  Persian (Iran) +	"ff_SN", //  Fulah (Senegal) +	"fi", //  Finnish +	"fi_FI", //  Finnish (Finland) +	"fil", //  Filipino +	"fil_PH", //  Filipino (Philippines) +	"fo_FO", //  Faroese (Faroe Islands) +	"fr", //  French +	"fr_BE", //  French (Belgium) +	"fr_CA", //  French (Canada) +	"fr_CH", //  French (Switzerland) +	"fr_FR", //  French (France) +	"fr_LU", //  French (Luxembourg) +	"fur_IT", //  Friulian (Italy) +	"fy_DE", //  Western Frisian (Germany) +	"fy_NL", //  Western Frisian (Netherlands) +	"ga", //  Irish +	"ga_IE", //  Irish (Ireland) +	"gd_GB", //  Scottish Gaelic (United Kingdom) +	"gez_ER", //  Geez (Eritrea) +	"gez_ET", //  Geez (Ethiopia) +	"gl_ES", //  Galician (Spain) +	"gu_IN", //  Gujarati (India) +	"gv_GB", //  Manx (United Kingdom) +	"hak_TW", //  Hakka Chinese (Taiwan) +	"ha_NG", //  Hausa (Nigeria) +	"he", //  Hebrew +	"he_IL", //  Hebrew (Israel) +	"hi", //  Hindi +	"hi_IN", //  Hindi (India) +	"hne_IN", //  Chhattisgarhi (India) +	"hr", //  Croatian +	"hr_HR", //  Croatian (Croatia) +	"hsb_DE", //  Upper Sorbian (Germany) +	"ht_HT", //  Haitian (Haiti) +	"hu", //  Hungarian +	"hu_HU", //  Hungarian (Hungary) +	"hus_MX", //  Huastec (Mexico) +	"hy_AM", //  Armenian (Armenia) +	"ia_FR", //  Interlingua (France) +	"id", //  Indonesian +	"id_ID", //  Indonesian (Indonesia) +	"ig_NG", //  Igbo (Nigeria) +	"ik_CA", //  Inupiaq (Canada) +	"is", //  Icelandic +	"is_IS", //  Icelandic (Iceland) +	"it", //  Italian +	"it_CH", //  Italian (Switzerland) +	"it_IT", //  Italian (Italy) +	"iu_CA", //  Inuktitut (Canada) +	"ja", //  Japanese +	"ja_JP", //  Japanese (Japan) +	"kab_DZ", //  Kabyle (Algeria) +	"ka", //  Georgian +	"ka_GE", //  Georgian (Georgia) +	"kk_KZ", //  Kazakh (Kazakhstan) +	"kl_GL", //  Kalaallisut (Greenland) +	"km_KH", //  Central Khmer (Cambodia) +	"kn_IN", //  Kannada (India) +	"kok_IN", //  Konkani (India) +	"ko", //  Korean +	"ko_KR", //  Korean (South Korea) +	"ks_IN", //  Kashmiri (India) +	"ku", //  Kurdish +	"ku_TR", //  Kurdish (Turkey) +	"kw_GB", //  Cornish (United Kingdom) +	"ky_KG", //  Kirghiz (Kyrgyzstan) +	"lb_LU", //  Luxembourgish (Luxembourg) +	"lg_UG", //  Ganda (Uganda) +	"li_BE", //  Limburgan (Belgium) +	"li_NL", //  Limburgan (Netherlands) +	"lij_IT", //  Ligurian (Italy) +	"ln_CD", //  Lingala (Congo) +	"lo_LA", //  Lao (Laos) +	"lt", //  Lithuanian +	"lt_LT", //  Lithuanian (Lithuania) +	"lv", //  Latvian +	"lv_LV", //  Latvian (Latvia) +	"lzh_TW", //  Literary Chinese (Taiwan) +	"mag_IN", //  Magahi (India) +	"mai_IN", //  Maithili (India) +	"mg_MG", //  Malagasy (Madagascar) +	"mh_MH", //  Marshallese (Marshall Islands) +	"mhr_RU", //  Eastern Mari (Russia) +	"mi", //  Māori +	"mi_NZ", //  Māori (New Zealand) +	"miq_NI", //  Mískito (Nicaragua) +	"mk", //  Macedonian +	"mk_MK", //  Macedonian (Macedonia) +	"ml", //  Malayalam +	"ml_IN", //  Malayalam (India) +	"mni_IN", //  Manipuri (India) +	"mn_MN", //  Mongolian (Mongolia) +	"mr_IN", //  Marathi (India) +	"ms", //  Malay +	"ms_MY", //  Malay (Malaysia) +	"mt", //  Maltese +	"mt_MT", //  Maltese (Malta) +	"my_MM", //  Burmese (Myanmar) +	"myv_RU", //  Erzya (Russia) +	"nah_MX", //  Nahuatl languages (Mexico) +	"nan_TW", //  Min Nan Chinese (Taiwan) +	"nb", //  Norwegian Bokmål +	"nb_NO", //  Norwegian Bokmål (Norway) +	"nds_DE", //  Low German (Germany) +	"nds_NL", //  Low German (Netherlands) +	"ne_NP", //  Nepali (Nepal) +	"nhn_MX", //  Central Nahuatl (Mexico) +	"niu_NU", //  Niuean (Niue) +	"niu_NZ", //  Niuean (New Zealand) +	"nl", //  Dutch +	"nl_AW", //  Dutch (Aruba) +	"nl_BE", //  Dutch (Belgium) +	"nl_NL", //  Dutch (Netherlands) +	"nn", //  Norwegian Nynorsk +	"nn_NO", //  Norwegian Nynorsk (Norway) +	"nr_ZA", //  South Ndebele (South Africa) +	"nso_ZA", //  Pedi (South Africa) +	"oc_FR", //  Occitan (France) +	"om", //  Oromo +	"om_ET", //  Oromo (Ethiopia) +	"om_KE", //  Oromo (Kenya) +	"or_IN", //  Oriya (India) +	"os_RU", //  Ossetian (Russia) +	"pa_IN", //  Panjabi (India) +	"pap", //  Papiamento +	"pap_AN", //  Papiamento (Netherlands Antilles) +	"pap_AW", //  Papiamento (Aruba) +	"pap_CW", //  Papiamento (Curaçao) +	"pa_PK", //  Panjabi (Pakistan) +	"pl", //  Polish +	"pl_PL", //  Polish (Poland) +	"pr", //  Pirate +	"ps_AF", //  Pushto (Afghanistan) +	"pt", //  Portuguese +	"pt_BR", //  Portuguese (Brazil) +	"pt_PT", //  Portuguese (Portugal) +	"quy_PE", //  Ayacucho Quechua (Peru) +	"quz_PE", //  Cusco Quechua (Peru) +	"raj_IN", //  Rajasthani (India) +	"ro", //  Romanian +	"ro_RO", //  Romanian (Romania) +	"ru", //  Russian +	"ru_RU", //  Russian (Russia) +	"ru_UA", //  Russian (Ukraine) +	"rw_RW", //  Kinyarwanda (Rwanda) +	"sa_IN", //  Sanskrit (India) +	"sat_IN", //  Santali (India) +	"sc_IT", //  Sardinian (Italy) +	"sco", //  Scots +	"sd_IN", //  Sindhi (India) +	"se_NO", //  Northern Sami (Norway) +	"sgs_LT", //  Samogitian (Lithuania) +	"shs_CA", //  Shuswap (Canada) +	"sid_ET", //  Sidamo (Ethiopia) +	"si", //  Sinhala +	"si_LK", //  Sinhala (Sri Lanka) +	"sk", //  Slovak +	"sk_SK", //  Slovak (Slovakia) +	"sl", //  Slovenian +	"sl_SI", //  Slovenian (Slovenia) +	"so", //  Somali +	"so_DJ", //  Somali (Djibouti) +	"so_ET", //  Somali (Ethiopia) +	"so_KE", //  Somali (Kenya) +	"so_SO", //  Somali (Somalia) +	"son_ML", //  Songhai languages (Mali) +	"sq", //  Albanian +	"sq_AL", //  Albanian (Albania) +	"sq_KV", //  Albanian (Kosovo) +	"sq_MK", //  Albanian (Macedonia) +	"sr", //  Serbian +	"sr_Cyrl", //  Serbian (Cyrillic) +	"sr_Latn", //  Serbian (Latin) +	"sr_ME", //  Serbian (Montenegro) +	"sr_RS", //  Serbian (Serbia) +	"ss_ZA", //  Swati (South Africa) +	"st_ZA", //  Southern Sotho (South Africa) +	"sv", //  Swedish +	"sv_FI", //  Swedish (Finland) +	"sv_SE", //  Swedish (Sweden) +	"sw_KE", //  Swahili (Kenya) +	"sw_TZ", //  Swahili (Tanzania) +	"szl_PL", //  Silesian (Poland) +	"ta", //  Tamil +	"ta_IN", //  Tamil (India) +	"ta_LK", //  Tamil (Sri Lanka) +	"tcy_IN", //  Tulu (India) +	"te", //  Telugu +	"te_IN", //  Telugu (India) +	"tg_TJ", //  Tajik (Tajikistan) +	"the_NP", //  Chitwania Tharu (Nepal) +	"th", //  Thai +	"th_TH", //  Thai (Thailand) +	"ti", //  Tigrinya +	"ti_ER", //  Tigrinya (Eritrea) +	"ti_ET", //  Tigrinya (Ethiopia) +	"tig_ER", //  Tigre (Eritrea) +	"tk_TM", //  Turkmen (Turkmenistan) +	"tl_PH", //  Tagalog (Philippines) +	"tn_ZA", //  Tswana (South Africa) +	"tr", //  Turkish +	"tr_CY", //  Turkish (Cyprus) +	"tr_TR", //  Turkish (Turkey) +	"ts_ZA", //  Tsonga (South Africa) +	"tt_RU", //  Tatar (Russia) +	"ug_CN", //  Uighur (China) +	"uk", //  Ukrainian +	"uk_UA", //  Ukrainian (Ukraine) +	"unm_US", //  Unami (United States) +	"ur", //  Urdu +	"ur_IN", //  Urdu (India) +	"ur_PK", //  Urdu (Pakistan) +	"uz", //  Uzbek +	"uz_UZ", //  Uzbek (Uzbekistan) +	"ve_ZA", //  Venda (South Africa) +	"vi", //  Vietnamese +	"vi_VN", //  Vietnamese (Vietnam) +	"wa_BE", //  Walloon (Belgium) +	"wae_CH", //  Walser (Switzerland) +	"wal_ET", //  Wolaytta (Ethiopia) +	"wo_SN", //  Wolof (Senegal) +	"xh_ZA", //  Xhosa (South Africa) +	"yi_US", //  Yiddish (United States) +	"yo_NG", //  Yoruba (Nigeria) +	"yue_HK", //  Yue Chinese (Hong Kong) +	"zh", //  Chinese +	"zh_CN", //  Chinese (China) +	"zh_HK", //  Chinese (Hong Kong) +	"zh_SG", //  Chinese (Singapore) +	"zh_TW", //  Chinese (Taiwan) +	"zu_ZA", //  Zulu (South Africa) +	nullptr +}; + +static const char *locale_names[] = { +	"Afar", +	"Afar (Djibouti)", +	"Afar (Eritrea)", +	"Afar (Ethiopia)", +	"Afrikaans", +	"Afrikaans (South Africa)", +	"Aguaruna (Peru)", +	"Akan (Ghana)", +	"Amharic (Ethiopia)", +	"Aragonese (Spain)", +	"Angika (India)", +	"Arabic", +	"Arabic (United Arab Emirates)", +	"Arabic (Bahrain)", +	"Arabic (Algeria)", +	"Arabic (Egypt)", +	"Arabic (India)", +	"Arabic (Iraq)", +	"Arabic (Jordan)", +	"Arabic (Kuwait)", +	"Arabic (Lebanon)", +	"Arabic (Libya)", +	"Arabic (Morocco)", +	"Arabic (Oman)", +	"Arabic (Qatar)", +	"Arabic (Saudi Arabia)", +	"Arabic (Sudan)", +	"Arabic (South Soudan)", +	"Arabic (Syria)", +	"Arabic (Tunisia)", +	"Arabic (Yemen)", +	"Assamese (India)", +	"Asturian (Spain)", +	"Southern Aymara (Peru)", +	"Aymara (Peru)", +	"Azerbaijani (Azerbaijan)", +	"Belarusian", +	"Belarusian (Belarus)", +	"Bemba (Zambia)", +	"Berber languages (Algeria)", +	"Berber languages (Morocco)", +	"Bulgarian", +	"Bulgarian (Bulgaria)", +	"Bhili (India)", +	"Bhojpuri (India)", +	"Bislama (Tuvalu)", +	"Bengali", +	"Bengali (Bangladesh)", +	"Bengali (India)", +	"Tibetan", +	"Tibetan (China)", +	"Tibetan (India)", +	"Breton (France)", +	"Bodo (India)", +	"Bosnian (Bosnia and Herzegovina)", +	"Bilin (Eritrea)", +	"Catalan", +	"Catalan (Andorra)", +	"Catalan (Spain)", +	"Catalan (France)", +	"Catalan (Italy)", +	"Chechen (Russia)", +	"Cherokee (United States)", +	"Mandarin Chinese (Taiwan)", +	"Crimean Tatar (Ukraine)", +	"Kashubian (Poland)", +	"Czech", +	"Czech (Czech Republic)", +	"Chuvash (Russia)", +	"Welsh (United Kingdom)", +	"Danish", +	"Danish (Denmark)", +	"German", +	"German (Austria)", +	"German (Belgium)", +	"German (Switzerland)", +	"German (Germany)", +	"German (Italy)", +	"German (Luxembourg)", +	"Dogri (India)", +	"Dhivehi (Maldives)", +	"Dzongkha (Bhutan)", +	"Greek", +	"Greek (Cyprus)", +	"Greek (Greece)", +	"English", +	"English (Antigua and Barbuda)", +	"English (Australia)", +	"English (Botswana)", +	"English (Canada)", +	"English (Denmark)", +	"English (United Kingdom)", +	"English (Hong Kong)", +	"English (Ireland)", +	"English (Israel)", +	"English (India)", +	"English (Nigeria)", +	"English (New Zealand)", +	"English (Philippines)", +	"English (Singapore)", +	"English (United States)", +	"English (South Africa)", +	"English (Zambia)", +	"English (Zimbabwe)", +	"Esperanto", +	"Spanish", +	"Spanish (Argentina)", +	"Spanish (Bolivia)", +	"Spanish (Chile)", +	"Spanish (Colombia)", +	"Spanish (Costa Rica)", +	"Spanish (Cuba)", +	"Spanish (Dominican Republic)", +	"Spanish (Ecuador)", +	"Spanish (Spain)", +	"Spanish (Guatemala)", +	"Spanish (Honduras)", +	"Spanish (Mexico)", +	"Spanish (Nicaragua)", +	"Spanish (Panama)", +	"Spanish (Peru)", +	"Spanish (Puerto Rico)", +	"Spanish (Paraguay)", +	"Spanish (El Salvador)", +	"Spanish (United States)", +	"Spanish (Uruguay)", +	"Spanish (Venezuela)", +	"Estonian", +	"Estonian (Estonia)", +	"Basque", +	"Basque (Spain)", +	"Persian", +	"Persian (Iran)", +	"Fulah (Senegal)", +	"Finnish", +	"Finnish (Finland)", +	"Filipino", +	"Filipino (Philippines)", +	"Faroese (Faroe Islands)", +	"French", +	"French (Belgium)", +	"French (Canada)", +	"French (Switzerland)", +	"French (France)", +	"French (Luxembourg)", +	"Friulian (Italy)", +	"Western Frisian (Germany)", +	"Western Frisian (Netherlands)", +	"Irish", +	"Irish (Ireland)", +	"Scottish Gaelic (United Kingdom)", +	"Geez (Eritrea)", +	"Geez (Ethiopia)", +	"Galician (Spain)", +	"Gujarati (India)", +	"Manx (United Kingdom)", +	"Hakka Chinese (Taiwan)", +	"Hausa (Nigeria)", +	"Hebrew", +	"Hebrew (Israel)", +	"Hindi", +	"Hindi (India)", +	"Chhattisgarhi (India)", +	"Croatian", +	"Croatian (Croatia)", +	"Upper Sorbian (Germany)", +	"Haitian (Haiti)", +	"Hungarian", +	"Hungarian (Hungary)", +	"Huastec (Mexico)", +	"Armenian (Armenia)", +	"Interlingua (France)", +	"Indonesian", +	"Indonesian (Indonesia)", +	"Igbo (Nigeria)", +	"Inupiaq (Canada)", +	"Icelandic", +	"Icelandic (Iceland)", +	"Italian", +	"Italian (Switzerland)", +	"Italian (Italy)", +	"Inuktitut (Canada)", +	"Japanese", +	"Japanese (Japan)", +	"Kabyle (Algeria)", +	"Georgian", +	"Georgian (Georgia)", +	"Kazakh (Kazakhstan)", +	"Kalaallisut (Greenland)", +	"Central Khmer (Cambodia)", +	"Kannada (India)", +	"Konkani (India)", +	"Korean", +	"Korean (South Korea)", +	"Kashmiri (India)", +	"Kurdish", +	"Kurdish (Turkey)", +	"Cornish (United Kingdom)", +	"Kirghiz (Kyrgyzstan)", +	"Luxembourgish (Luxembourg)", +	"Ganda (Uganda)", +	"Limburgan (Belgium)", +	"Limburgan (Netherlands)", +	"Ligurian (Italy)", +	"Lingala (Congo)", +	"Lao (Laos)", +	"Lithuanian", +	"Lithuanian (Lithuania)", +	"Latvian", +	"Latvian (Latvia)", +	"Literary Chinese (Taiwan)", +	"Magahi (India)", +	"Maithili (India)", +	"Malagasy (Madagascar)", +	"Marshallese (Marshall Islands)", +	"Eastern Mari (Russia)", +	"Māori", +	"Māori (New Zealand)", +	"Mískito (Nicaragua)", +	"Macedonian", +	"Macedonian (Macedonia)", +	"Malayalam", +	"Malayalam (India)", +	"Manipuri (India)", +	"Mongolian (Mongolia)", +	"Marathi (India)", +	"Malay", +	"Malay (Malaysia)", +	"Maltese", +	"Maltese (Malta)", +	"Burmese (Myanmar)", +	"Erzya (Russia)", +	"Nahuatl languages (Mexico)", +	"Min Nan Chinese (Taiwan)", +	"Norwegian Bokmål", +	"Norwegian Bokmål (Norway)", +	"Low German (Germany)", +	"Low German (Netherlands)", +	"Nepali (Nepal)", +	"Central Nahuatl (Mexico)", +	"Niuean (Niue)", +	"Niuean (New Zealand)", +	"Dutch", +	"Dutch (Aruba)", +	"Dutch (Belgium)", +	"Dutch (Netherlands)", +	"Norwegian Nynorsk", +	"Norwegian Nynorsk (Norway)", +	"South Ndebele (South Africa)", +	"Pedi (South Africa)", +	"Occitan (France)", +	"Oromo", +	"Oromo (Ethiopia)", +	"Oromo (Kenya)", +	"Oriya (India)", +	"Ossetian (Russia)", +	"Panjabi (India)", +	"Papiamento", +	"Papiamento (Netherlands Antilles)", +	"Papiamento (Aruba)", +	"Papiamento (Curaçao)", +	"Panjabi (Pakistan)", +	"Polish", +	"Polish (Poland)", +	"Pirate", +	"Pushto (Afghanistan)", +	"Portuguese", +	"Portuguese (Brazil)", +	"Portuguese (Portugal)", +	"Ayacucho Quechua (Peru)", +	"Cusco Quechua (Peru)", +	"Rajasthani (India)", +	"Romanian", +	"Romanian (Romania)", +	"Russian", +	"Russian (Russia)", +	"Russian (Ukraine)", +	"Kinyarwanda (Rwanda)", +	"Sanskrit (India)", +	"Santali (India)", +	"Sardinian (Italy)", +	"Scots (Scotland)", +	"Sindhi (India)", +	"Northern Sami (Norway)", +	"Samogitian (Lithuania)", +	"Shuswap (Canada)", +	"Sidamo (Ethiopia)", +	"Sinhala", +	"Sinhala (Sri Lanka)", +	"Slovak", +	"Slovak (Slovakia)", +	"Slovenian", +	"Slovenian (Slovenia)", +	"Somali", +	"Somali (Djibouti)", +	"Somali (Ethiopia)", +	"Somali (Kenya)", +	"Somali (Somalia)", +	"Songhai languages (Mali)", +	"Albanian", +	"Albanian (Albania)", +	"Albanian (Kosovo)", +	"Albanian (Macedonia)", +	"Serbian", +	"Serbian (Cyrillic)", +	"Serbian (Latin)", +	"Serbian (Montenegro)", +	"Serbian (Serbia)", +	"Swati (South Africa)", +	"Southern Sotho (South Africa)", +	"Swedish", +	"Swedish (Finland)", +	"Swedish (Sweden)", +	"Swahili (Kenya)", +	"Swahili (Tanzania)", +	"Silesian (Poland)", +	"Tamil", +	"Tamil (India)", +	"Tamil (Sri Lanka)", +	"Tulu (India)", +	"Telugu", +	"Telugu (India)", +	"Tajik (Tajikistan)", +	"Chitwania Tharu (Nepal)", +	"Thai", +	"Thai (Thailand)", +	"Tigrinya", +	"Tigrinya (Eritrea)", +	"Tigrinya (Ethiopia)", +	"Tigre (Eritrea)", +	"Turkmen (Turkmenistan)", +	"Tagalog (Philippines)", +	"Tswana (South Africa)", +	"Turkish", +	"Turkish (Cyprus)", +	"Turkish (Turkey)", +	"Tsonga (South Africa)", +	"Tatar (Russia)", +	"Uighur (China)", +	"Ukrainian", +	"Ukrainian (Ukraine)", +	"Unami (United States)", +	"Urdu", +	"Urdu (India)", +	"Urdu (Pakistan)", +	"Uzbek", +	"Uzbek (Uzbekistan)", +	"Venda (South Africa)", +	"Vietnamese", +	"Vietnamese (Vietnam)", +	"Walloon (Belgium)", +	"Walser (Switzerland)", +	"Wolaytta (Ethiopia)", +	"Wolof (Senegal)", +	"Xhosa (South Africa)", +	"Yiddish (United States)", +	"Yoruba (Nigeria)", +	"Yue Chinese (Hong Kong)", +	"Chinese", +	"Chinese (China)", +	"Chinese (Hong Kong)", +	"Chinese (Singapore)", +	"Chinese (Taiwan)", +	"Zulu (South Africa)", +	nullptr +}; + +// Windows has some weird locale identifiers which do not honor the ISO 639-1 +// standardized nomenclature. Whenever those don't conflict with existing ISO +// identifiers, we override them. +// +// Reference: +// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms693062(v=vs.85).aspx + +static const char *locale_renames[][2] = { +	{ "in", "id" }, //  Indonesian +	{ "iw", "he" }, //  Hebrew +	{ "no", "nb" }, //  Norwegian Bokmål +	{ nullptr, nullptr } +}; + +/////////////////////////////////////////////// + +Dictionary Translation::_get_messages() const { +	Dictionary d; +	for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) { +		d[E->key()] = E->value(); +	} +	return d; +} + +Vector<String> Translation::_get_message_list() const { +	Vector<String> msgs; +	msgs.resize(translation_map.size()); +	int idx = 0; +	for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) { +		msgs.set(idx, E->key()); +		idx += 1; +	} + +	return msgs; +} + +void Translation::_set_messages(const Dictionary &p_messages) { +	List<Variant> keys; +	p_messages.get_key_list(&keys); +	for (auto E = keys.front(); E; E = E->next()) { +		translation_map[E->get()] = p_messages[E->get()]; +	} +} + +void Translation::set_locale(const String &p_locale) { +	String univ_locale = TranslationServer::standardize_locale(p_locale); + +	if (!TranslationServer::is_locale_valid(univ_locale)) { +		String trimmed_locale = TranslationServer::get_language_code(univ_locale); + +		ERR_FAIL_COND_MSG(!TranslationServer::is_locale_valid(trimmed_locale), "Invalid locale: " + trimmed_locale + "."); + +		locale = trimmed_locale; +	} else { +		locale = univ_locale; +	} + +	if (OS::get_singleton()->get_main_loop()) { +		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); +	} +} + +void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) { +	translation_map[p_src_text] = p_xlated_text; +} + +void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) { +	WARN_PRINT("Translation class doesn't handle plural messages. Calling add_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class"); +	ERR_FAIL_COND_MSG(p_plural_xlated_texts.empty(), "Parameter vector p_plural_xlated_texts passed in is empty."); +	translation_map[p_src_text] = p_plural_xlated_texts[0]; +} + +StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const { +	if (p_context != StringName()) { +		WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class"); +	} + +	const Map<StringName, StringName>::Element *E = translation_map.find(p_src_text); +	if (!E) { +		return StringName(); +	} + +	return E->get(); +} + +StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const { +	WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class"); +	return get_message(p_src_text); +} + +void Translation::erase_message(const StringName &p_src_text, const StringName &p_context) { +	if (p_context != StringName()) { +		WARN_PRINT("Translation class doesn't handle context. Using context in erase_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class"); +	} + +	translation_map.erase(p_src_text); +} + +void Translation::get_message_list(List<StringName> *r_messages) const { +	for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) { +		r_messages->push_back(E->key()); +	} +} + +int Translation::get_message_count() const { +	return translation_map.size(); +} + +void Translation::_bind_methods() { +	ClassDB::bind_method(D_METHOD("set_locale", "locale"), &Translation::set_locale); +	ClassDB::bind_method(D_METHOD("get_locale"), &Translation::get_locale); +	ClassDB::bind_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL("")); +	ClassDB::bind_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL("")); +	ClassDB::bind_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL("")); +	ClassDB::bind_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL("")); +	ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL("")); +	ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list); +	ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count); +	ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages); +	ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages); + +	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages"); +	ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale"); +} + +/////////////////////////////////////////////// + +bool TranslationServer::is_locale_valid(const String &p_locale) { +	const char **ptr = locale_list; + +	while (*ptr) { +		if (*ptr == p_locale) { +			return true; +		} +		ptr++; +	} + +	return false; +} + +String TranslationServer::standardize_locale(const String &p_locale) { +	// Replaces '-' with '_' for macOS Sierra-style locales +	String univ_locale = p_locale.replace("-", "_"); + +	// Handles known non-ISO locale names used e.g. on Windows +	int idx = 0; +	while (locale_renames[idx][0] != nullptr) { +		if (locale_renames[idx][0] == univ_locale) { +			univ_locale = locale_renames[idx][1]; +			break; +		} +		idx++; +	} + +	return univ_locale; +} + +String TranslationServer::get_language_code(const String &p_locale) { +	ERR_FAIL_COND_V_MSG(p_locale.length() < 2, p_locale, "Invalid locale '" + p_locale + "'."); +	// Most language codes are two letters, but some are three, +	// so we have to look for a regional code separator ('_' or '-') +	// to extract the left part. +	// For example we get 'nah_MX' as input and should return 'nah'. +	int split = p_locale.find("_"); +	if (split == -1) { +		split = p_locale.find("-"); +	} +	if (split == -1) { // No separator, so the locale is already only a language code. +		return p_locale; +	} +	return p_locale.left(split); +} + +void TranslationServer::set_locale(const String &p_locale) { +	String univ_locale = standardize_locale(p_locale); + +	if (!is_locale_valid(univ_locale)) { +		String trimmed_locale = get_language_code(univ_locale); +		print_verbose(vformat("Unsupported locale '%s', falling back to '%s'.", p_locale, trimmed_locale)); + +		if (!is_locale_valid(trimmed_locale)) { +			ERR_PRINT(vformat("Unsupported locale '%s', falling back to 'en'.", trimmed_locale)); +			locale = "en"; +		} else { +			locale = trimmed_locale; +		} +	} else { +		locale = univ_locale; +	} + +	if (OS::get_singleton()->get_main_loop()) { +		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); +	} + +	ResourceLoader::reload_translation_remaps(); +} + +String TranslationServer::get_locale() const { +	return locale; +} + +String TranslationServer::get_locale_name(const String &p_locale) const { +	if (!locale_name_map.has(p_locale)) { +		return String(); +	} +	return locale_name_map[p_locale]; +} + +Array TranslationServer::get_loaded_locales() const { +	Array locales; +	for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) { +		const Ref<Translation> &t = E->get(); +		ERR_FAIL_COND_V(t.is_null(), Array()); +		String l = t->get_locale(); + +		locales.push_back(l); +	} + +	return locales; +} + +Vector<String> TranslationServer::get_all_locales() { +	Vector<String> locales; + +	const char **ptr = locale_list; + +	while (*ptr) { +		locales.push_back(*ptr); +		ptr++; +	} + +	return locales; +} + +Vector<String> TranslationServer::get_all_locale_names() { +	Vector<String> locales; + +	const char **ptr = locale_names; + +	while (*ptr) { +		locales.push_back(String::utf8(*ptr)); +		ptr++; +	} + +	return locales; +} + +void TranslationServer::add_translation(const Ref<Translation> &p_translation) { +	translations.insert(p_translation); +} + +void TranslationServer::remove_translation(const Ref<Translation> &p_translation) { +	translations.erase(p_translation); +} + +Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) { +	Ref<Translation> res; +	String lang = get_language_code(p_locale); +	bool near_match_found = false; + +	for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) { +		const Ref<Translation> &t = E->get(); +		ERR_FAIL_COND_V(t.is_null(), nullptr); +		String l = t->get_locale(); + +		// Exact match. +		if (l == p_locale) { +			return t; +		} + +		// If near match found, keep that match, but keep looking to try to look for perfect match. +		if (get_language_code(l) == lang && !near_match_found) { +			res = t; +			near_match_found = true; +		} +	} +	return res; +} + +void TranslationServer::clear() { +	translations.clear(); +} + +StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const { +	// Match given message against the translation catalog for the project locale. + +	if (!enabled) { +		return p_message; +	} + +	ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid."); + +	StringName res = _get_message_from_translations(p_message, p_context, locale, false); + +	if (!res && fallback.length() >= 2) { +		res = _get_message_from_translations(p_message, p_context, fallback, false); +	} + +	if (!res) { +		return p_message; +	} + +	return res; +} + +StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { +	if (!enabled) { +		if (p_n == 1) { +			return p_message; +		} +		return p_message_plural; +	} + +	ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid."); + +	StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n); + +	if (!res && fallback.length() >= 2) { +		res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n); +	} + +	if (!res) { +		if (p_n == 1) { +			return p_message; +		} +		return p_message_plural; +	} + +	return res; +} + +StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const { +	// Locale can be of the form 'll_CC', i.e. language code and regional code, +	// e.g. 'en_US', 'en_GB', etc. It might also be simply 'll', e.g. 'en'. +	// To find the relevant translation, we look for those with locale starting +	// with the language code, and then if any is an exact match for the long +	// form. If not found, we fall back to a near match (another locale with +	// same language code). + +	// Note: ResourceLoader::_path_remap reproduces this locale near matching +	// logic, so be sure to propagate changes there when changing things here. + +	StringName res; +	String lang = get_language_code(p_locale); +	bool near_match = false; + +	for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) { +		const Ref<Translation> &t = E->get(); +		ERR_FAIL_COND_V(t.is_null(), p_message); +		String l = t->get_locale(); + +		bool exact_match = (l == p_locale); +		if (!exact_match) { +			if (near_match) { +				continue; // Only near-match once, but keep looking for exact matches. +			} +			if (get_language_code(l) != lang) { +				continue; // Language code does not match. +			} +		} + +		StringName r; +		if (!plural) { +			r = t->get_message(p_message, p_context); +		} else { +			r = t->get_plural_message(p_message, p_message_plural, p_n, p_context); +		} + +		if (!r) { +			continue; +		} +		res = r; + +		if (exact_match) { +			break; +		} else { +			near_match = true; +		} +	} + +	return res; +} + +TranslationServer *TranslationServer::singleton = nullptr; + +bool TranslationServer::_load_translations(const String &p_from) { +	if (ProjectSettings::get_singleton()->has_setting(p_from)) { +		Vector<String> translations = ProjectSettings::get_singleton()->get(p_from); + +		int tcount = translations.size(); + +		if (tcount) { +			const String *r = translations.ptr(); + +			for (int i = 0; i < tcount; i++) { +				Ref<Translation> tr = ResourceLoader::load(r[i]); +				if (tr.is_valid()) { +					add_translation(tr); +				} +			} +		} +		return true; +	} + +	return false; +} + +void TranslationServer::setup() { +	String test = GLOBAL_DEF("locale/test", ""); +	test = test.strip_edges(); +	if (test != "") { +		set_locale(test); +	} else { +		set_locale(OS::get_singleton()->get_locale()); +	} +	fallback = GLOBAL_DEF("locale/fallback", "en"); +#ifdef TOOLS_ENABLED +	{ +		String options = ""; +		int idx = 0; +		while (locale_list[idx]) { +			if (idx > 0) { +				options += ","; +			} +			options += locale_list[idx]; +			idx++; +		} +		ProjectSettings::get_singleton()->set_custom_property_info("locale/fallback", PropertyInfo(Variant::STRING, "locale/fallback", PROPERTY_HINT_ENUM, options)); +	} +#endif +} + +void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) { +	tool_translation = p_translation; +} + +StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const { +	if (tool_translation.is_valid()) { +		StringName r = tool_translation->get_message(p_message, p_context); +		if (r) { +			return r; +		} +	} +	return p_message; +} + +StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { +	if (tool_translation.is_valid()) { +		StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); +		if (r) { +			return r; +		} +	} + +	if (p_n == 1) { +		return p_message; +	} +	return p_message_plural; +} + +void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) { +	doc_translation = p_translation; +} + +StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const { +	if (doc_translation.is_valid()) { +		StringName r = doc_translation->get_message(p_message, p_context); +		if (r) { +			return r; +		} +	} +	return p_message; +} + +StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { +	if (doc_translation.is_valid()) { +		StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); +		if (r) { +			return r; +		} +	} + +	if (p_n == 1) { +		return p_message; +	} +	return p_message_plural; +} + +void TranslationServer::_bind_methods() { +	ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale); +	ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale); + +	ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name); + +	ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL("")); +	ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL("")); + +	ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation); +	ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation); +	ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object); + +	ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear); + +	ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales); +} + +void TranslationServer::load_translations() { +	String locale = get_locale(); +	_load_translations("locale/translations"); //all +	_load_translations("locale/translations_" + locale.substr(0, 2)); + +	if (locale.substr(0, 2) != locale) { +		_load_translations("locale/translations_" + locale); +	} +} + +TranslationServer::TranslationServer() { +	singleton = this; + +	for (int i = 0; locale_list[i]; ++i) { +		locale_name_map.insert(locale_list[i], String::utf8(locale_names[i])); +	} +} diff --git a/core/string/translation.h b/core/string/translation.h new file mode 100644 index 0000000000..8d34f7997e --- /dev/null +++ b/core/string/translation.h @@ -0,0 +1,129 @@ +/*************************************************************************/ +/*  translation.h                                                        */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef TRANSLATION_H +#define TRANSLATION_H + +#include "core/io/resource.h" + +class Translation : public Resource { +	GDCLASS(Translation, Resource); +	OBJ_SAVE_TYPE(Translation); +	RES_BASE_EXTENSION("translation"); + +	String locale = "en"; +	Map<StringName, StringName> translation_map; + +	virtual Vector<String> _get_message_list() const; +	virtual Dictionary _get_messages() const; +	virtual void _set_messages(const Dictionary &p_messages); + +protected: +	static void _bind_methods(); + +public: +	void set_locale(const String &p_locale); +	_FORCE_INLINE_ String get_locale() const { return locale; } + +	virtual void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = ""); +	virtual void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = ""); +	virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const; //overridable for other implementations +	virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const; +	virtual void erase_message(const StringName &p_src_text, const StringName &p_context = ""); +	virtual void get_message_list(List<StringName> *r_messages) const; +	virtual int get_message_count() const; + +	Translation() {} +}; + +class TranslationServer : public Object { +	GDCLASS(TranslationServer, Object); + +	String locale = "en"; +	String fallback; + +	Set<Ref<Translation>> translations; +	Ref<Translation> tool_translation; +	Ref<Translation> doc_translation; + +	Map<String, String> locale_name_map; + +	bool enabled = true; + +	static TranslationServer *singleton; +	bool _load_translations(const String &p_from); + +	StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const; + +	static void _bind_methods(); + +public: +	_FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; } + +	void set_enabled(bool p_enabled) { enabled = p_enabled; } +	_FORCE_INLINE_ bool is_enabled() const { return enabled; } + +	void set_locale(const String &p_locale); +	String get_locale() const; +	Ref<Translation> get_translation_object(const String &p_locale); + +	String get_locale_name(const String &p_locale) const; + +	Array get_loaded_locales() const; + +	void add_translation(const Ref<Translation> &p_translation); +	void remove_translation(const Ref<Translation> &p_translation); + +	StringName translate(const StringName &p_message, const StringName &p_context = "") const; +	StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; + +	static Vector<String> get_all_locales(); +	static Vector<String> get_all_locale_names(); +	static bool is_locale_valid(const String &p_locale); +	static String standardize_locale(const String &p_locale); +	static String get_language_code(const String &p_locale); + +	void set_tool_translation(const Ref<Translation> &p_translation); +	StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const; +	StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; +	void set_doc_translation(const Ref<Translation> &p_translation); +	StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const; +	StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; + +	void setup(); + +	void clear(); + +	void load_translations(); + +	TranslationServer(); +}; + +#endif // TRANSLATION_H diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp new file mode 100644 index 0000000000..203f29026b --- /dev/null +++ b/core/string/translation_po.cpp @@ -0,0 +1,312 @@ +/*************************************************************************/ +/*  translation_po.cpp                                                   */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "translation_po.h" + +#include "core/os/file_access.h" + +#ifdef DEBUG_TRANSLATION_PO +void TranslationPO::print_translation_map() { +	Error err; +	FileAccess *file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err); +	if (err != OK) { +		ERR_PRINT("Failed to open translation_map_print_test.txt"); +		return; +	} + +	file->store_line("NPlural : " + String::num_int64(this->get_plural_forms())); +	file->store_line("Plural rule : " + this->get_plural_rule()); +	file->store_line(""); + +	List<StringName> context_l; +	translation_map.get_key_list(&context_l); +	for (auto E = context_l.front(); E; E = E->next()) { +		StringName ctx = E->get(); +		file->store_line(" ===== Context: " + String::utf8(String(ctx).utf8()) + " ===== "); +		const HashMap<StringName, Vector<StringName>> &inner_map = translation_map[ctx]; + +		List<StringName> id_l; +		inner_map.get_key_list(&id_l); +		for (auto E2 = id_l.front(); E2; E2 = E2->next()) { +			StringName id = E2->get(); +			file->store_line("msgid: " + String::utf8(String(id).utf8())); +			for (int i = 0; i < inner_map[id].size(); i++) { +				file->store_line("msgstr[" + String::num_int64(i) + "]: " + String::utf8(String(inner_map[id][i]).utf8())); +			} +			file->store_line(""); +		} +	} +	file->close(); +} +#endif + +Dictionary TranslationPO::_get_messages() const { +	// Return translation_map as a Dictionary. + +	Dictionary d; + +	List<StringName> context_l; +	translation_map.get_key_list(&context_l); +	for (auto E = context_l.front(); E; E = E->next()) { +		StringName ctx = E->get(); +		const HashMap<StringName, Vector<StringName>> &id_str_map = translation_map[ctx]; + +		Dictionary d2; +		List<StringName> id_l; +		id_str_map.get_key_list(&id_l); +		// Save list of id and strs associated with a context in a temporary dictionary. +		for (auto E2 = id_l.front(); E2; E2 = E2->next()) { +			StringName id = E2->get(); +			d2[id] = id_str_map[id]; +		} + +		d[ctx] = d2; +	} + +	return d; +} + +void TranslationPO::_set_messages(const Dictionary &p_messages) { +	// Construct translation_map from a Dictionary. + +	List<Variant> context_l; +	p_messages.get_key_list(&context_l); +	for (auto E = context_l.front(); E; E = E->next()) { +		StringName ctx = E->get(); +		const Dictionary &id_str_map = p_messages[ctx]; + +		HashMap<StringName, Vector<StringName>> temp_map; +		List<Variant> id_l; +		id_str_map.get_key_list(&id_l); +		for (auto E2 = id_l.front(); E2; E2 = E2->next()) { +			StringName id = E2->get(); +			temp_map[id] = id_str_map[id]; +		} + +		translation_map[ctx] = temp_map; +	} +} + +Vector<String> TranslationPO::_get_message_list() const { +	// Return all keys in translation_map. + +	List<StringName> msgs; +	get_message_list(&msgs); + +	Vector<String> v; +	for (auto E = msgs.front(); E; E = E->next()) { +		v.push_back(E->get()); +	} + +	return v; +} + +int TranslationPO::_get_plural_index(int p_n) const { +	// Get a number between [0;number of plural forms). + +	input_val.clear(); +	input_val.push_back(p_n); + +	Variant result; +	for (int i = 0; i < equi_tests.size(); i++) { +		Error err = expr->parse(equi_tests[i], input_name); +		ERR_FAIL_COND_V_MSG(err != OK, 0, "Cannot parse expression. Error: " + expr->get_error_text()); + +		result = expr->execute(input_val); +		ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, "Cannot evaluate expression."); + +		// Last expression. Variant result will either map to a bool or an integer, in both cases returning it will give the correct plural index. +		if (i + 1 == equi_tests.size()) { +			return result; +		} + +		if (bool(result)) { +			return i; +		} +	} + +	ERR_FAIL_V_MSG(0, "Unexpected. Function should have returned. Please report this bug."); +} + +void TranslationPO::_cache_plural_tests(const String &p_plural_rule) { +	// Some examples of p_plural_rule passed in can have the form: +	// "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5" (Arabic) +	// "n >= 2" (French) // When evaluating the last, esp careful with this one. +	// "n != 1" (English) +	int first_ques_mark = p_plural_rule.find("?"); +	if (first_ques_mark == -1) { +		equi_tests.push_back(p_plural_rule.strip_edges()); +		return; +	} + +	String equi_test = p_plural_rule.substr(0, first_ques_mark).strip_edges(); +	equi_tests.push_back(equi_test); + +	String after_colon = p_plural_rule.substr(p_plural_rule.find(":") + 1, p_plural_rule.length()); +	_cache_plural_tests(after_colon); +} + +void TranslationPO::set_plural_rule(const String &p_plural_rule) { +	// Set plural_forms and plural_rule. +	// p_plural_rule passed in has the form "Plural-Forms: nplurals=2; plural=(n >= 2);". + +	int first_semi_col = p_plural_rule.find(";"); +	plural_forms = p_plural_rule.substr(p_plural_rule.find("=") + 1, first_semi_col - (p_plural_rule.find("=") + 1)).to_int(); + +	int expression_start = p_plural_rule.find("=", first_semi_col) + 1; +	int second_semi_col = p_plural_rule.rfind(";"); +	plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start); + +	// Setup the cache to make evaluating plural rule faster later on. +	plural_rule = plural_rule.replacen("(", ""); +	plural_rule = plural_rule.replacen(")", ""); +	_cache_plural_tests(plural_rule); +	expr.instance(); +	input_name.push_back("n"); +} + +void TranslationPO::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) { +	HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context]; + +	if (map_id_str.has(p_src_text)) { +		WARN_PRINT("Double translations for \"" + String(p_src_text) + "\" under the same context \"" + String(p_context) + "\" for locale \"" + get_locale() + "\".\nThere should only be one unique translation for a given string under the same context."); +		map_id_str[p_src_text].set(0, p_xlated_text); +	} else { +		map_id_str[p_src_text].push_back(p_xlated_text); +	} +} + +void TranslationPO::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) { +	ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, "Trying to add plural texts that don't match the required number of plural forms for locale \"" + get_locale() + "\""); + +	HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context]; + +	if (map_id_str.has(p_src_text)) { +		WARN_PRINT("Double translations for \"" + p_src_text + "\" under the same context \"" + p_context + "\" for locale " + get_locale() + ".\nThere should only be one unique translation for a given string under the same context."); +		map_id_str[p_src_text].clear(); +	} + +	for (int i = 0; i < p_plural_xlated_texts.size(); i++) { +		map_id_str[p_src_text].push_back(p_plural_xlated_texts[i]); +	} +} + +int TranslationPO::get_plural_forms() const { +	return plural_forms; +} + +String TranslationPO::get_plural_rule() const { +	return plural_rule; +} + +StringName TranslationPO::get_message(const StringName &p_src_text, const StringName &p_context) const { +	if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) { +		return StringName(); +	} +	ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug."); + +	return translation_map[p_context][p_src_text][0]; +} + +StringName TranslationPO::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const { +	ERR_FAIL_COND_V_MSG(p_n < 0, StringName(), "N passed into translation to get a plural message should not be negative. For negative numbers, use singular translation please. Search \"gettext PO Plural Forms\" online for the documentation on translating negative numbers."); + +	// If the query is the same as last time, return the cached result. +	if (p_n == last_plural_n && p_context == last_plural_context && p_src_text == last_plural_key) { +		return translation_map[p_context][p_src_text][last_plural_mapped_index]; +	} + +	if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) { +		return StringName(); +	} +	ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug."); + +	if (translation_map[p_context][p_src_text].size() == 1) { +		WARN_PRINT("Source string \"" + String(p_src_text) + "\" doesn't have plural translations. Use singular translation API for such as tr(), TTR() to translate \"" + String(p_src_text) + "\""); +		return translation_map[p_context][p_src_text][0]; +	} + +	int plural_index = _get_plural_index(p_n); +	ERR_FAIL_COND_V_MSG(plural_index < 0 || translation_map[p_context][p_src_text].size() < plural_index + 1, StringName(), "Plural index returned or number of plural translations is not valid. Please report this bug."); + +	// Cache result so that if the next entry is the same, we can return directly. +	// _get_plural_index(p_n) can get very costly, especially when evaluating long plural-rule (Arabic) +	last_plural_key = p_src_text; +	last_plural_context = p_context; +	last_plural_n = p_n; +	last_plural_mapped_index = plural_index; + +	return translation_map[p_context][p_src_text][plural_index]; +} + +void TranslationPO::erase_message(const StringName &p_src_text, const StringName &p_context) { +	if (!translation_map.has(p_context)) { +		return; +	} + +	translation_map[p_context].erase(p_src_text); +} + +void TranslationPO::get_message_list(List<StringName> *r_messages) const { +	// PHashTranslation uses this function to get the list of msgid. +	// Return all the keys of translation_map under "" context. + +	List<StringName> context_l; +	translation_map.get_key_list(&context_l); + +	for (auto E = context_l.front(); E; E = E->next()) { +		if (String(E->get()) != "") { +			continue; +		} + +		List<StringName> msgid_l; +		translation_map[E->get()].get_key_list(&msgid_l); + +		for (auto E2 = msgid_l.front(); E2; E2 = E2->next()) { +			r_messages->push_back(E2->get()); +		} +	} +} + +int TranslationPO::get_message_count() const { +	List<StringName> context_l; +	translation_map.get_key_list(&context_l); + +	int count = 0; +	for (auto E = context_l.front(); E; E = E->next()) { +		count += translation_map[E->get()].size(); +	} +	return count; +} + +void TranslationPO::_bind_methods() { +	ClassDB::bind_method(D_METHOD("get_plural_forms"), &TranslationPO::get_plural_forms); +	ClassDB::bind_method(D_METHOD("get_plural_rule"), &TranslationPO::get_plural_rule); +} diff --git a/core/string/translation_po.h b/core/string/translation_po.h new file mode 100644 index 0000000000..c8a47bec5a --- /dev/null +++ b/core/string/translation_po.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/*  translation_po.h                                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef TRANSLATION_PO_H +#define TRANSLATION_PO_H + +//#define DEBUG_TRANSLATION_PO + +#include "core/math/expression.h" +#include "core/string/translation.h" + +class TranslationPO : public Translation { +	GDCLASS(TranslationPO, Translation); + +	// TLDR: Maps context to a list of source strings and translated strings. In PO terms, maps msgctxt to a list of msgid and msgstr. +	// The first key corresponds to context, and the second key (of the contained HashMap) corresponds to source string. +	// The value Vector<StringName> in the second map stores the translated strings. Index 0, 1, 2 matches msgstr[0], msgstr[1], msgstr[2]... in the case of plurals. +	// Otherwise index 0 matches to msgstr in a singular translation. +	// Strings without context have "" as first key. +	HashMap<StringName, HashMap<StringName, Vector<StringName>>> translation_map; + +	int plural_forms = 0; // 0 means no "Plural-Forms" is given in the PO header file. The min for all languages is 1. +	String plural_rule; + +	// Cache temporary variables related to _get_plural_index() to make it faster +	Vector<String> equi_tests; +	Vector<String> input_name; +	mutable Ref<Expression> expr; +	mutable Array input_val; +	mutable StringName last_plural_key; +	mutable StringName last_plural_context; +	mutable int last_plural_n = -1; // Set it to an impossible value at the beginning. +	mutable int last_plural_mapped_index = 0; + +	void _cache_plural_tests(const String &p_plural_rule); +	int _get_plural_index(int p_n) const; + +	Vector<String> _get_message_list() const override; +	Dictionary _get_messages() const override; +	void _set_messages(const Dictionary &p_messages) override; + +protected: +	static void _bind_methods(); + +public: +	void get_message_list(List<StringName> *r_messages) const override; +	int get_message_count() const override; +	void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "") override; +	void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = "") override; +	StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override; +	StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override; +	void erase_message(const StringName &p_src_text, const StringName &p_context = "") override; + +	void set_plural_rule(const String &p_plural_rule); +	int get_plural_forms() const; +	String get_plural_rule() const; + +#ifdef DEBUG_TRANSLATION_PO +	void print_translation_map(); +#endif + +	TranslationPO() {} +}; + +#endif // TRANSLATION_PO_H diff --git a/core/string/ucaps.h b/core/string/ucaps.h new file mode 100644 index 0000000000..79b346acba --- /dev/null +++ b/core/string/ucaps.h @@ -0,0 +1,1415 @@ +/*************************************************************************/ +/*  ucaps.h                                                              */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef UCAPS_H +#define UCAPS_H + +//satan invented unicode? +#define CAPS_LEN 666 + +static const int caps_table[CAPS_LEN][2] = { +	{ 0x0061, 0x0041 }, +	{ 0x0062, 0x0042 }, +	{ 0x0063, 0x0043 }, +	{ 0x0064, 0x0044 }, +	{ 0x0065, 0x0045 }, +	{ 0x0066, 0x0046 }, +	{ 0x0067, 0x0047 }, +	{ 0x0068, 0x0048 }, +	{ 0x0069, 0x0049 }, +	{ 0x006A, 0x004A }, +	{ 0x006B, 0x004B }, +	{ 0x006C, 0x004C }, +	{ 0x006D, 0x004D }, +	{ 0x006E, 0x004E }, +	{ 0x006F, 0x004F }, +	{ 0x0070, 0x0050 }, +	{ 0x0071, 0x0051 }, +	{ 0x0072, 0x0052 }, +	{ 0x0073, 0x0053 }, +	{ 0x0074, 0x0054 }, +	{ 0x0075, 0x0055 }, +	{ 0x0076, 0x0056 }, +	{ 0x0077, 0x0057 }, +	{ 0x0078, 0x0058 }, +	{ 0x0079, 0x0059 }, +	{ 0x007A, 0x005A }, +	{ 0x00E0, 0x00C0 }, +	{ 0x00E1, 0x00C1 }, +	{ 0x00E2, 0x00C2 }, +	{ 0x00E3, 0x00C3 }, +	{ 0x00E4, 0x00C4 }, +	{ 0x00E5, 0x00C5 }, +	{ 0x00E6, 0x00C6 }, +	{ 0x00E7, 0x00C7 }, +	{ 0x00E8, 0x00C8 }, +	{ 0x00E9, 0x00C9 }, +	{ 0x00EA, 0x00CA }, +	{ 0x00EB, 0x00CB }, +	{ 0x00EC, 0x00CC }, +	{ 0x00ED, 0x00CD }, +	{ 0x00EE, 0x00CE }, +	{ 0x00EF, 0x00CF }, +	{ 0x00F0, 0x00D0 }, +	{ 0x00F1, 0x00D1 }, +	{ 0x00F2, 0x00D2 }, +	{ 0x00F3, 0x00D3 }, +	{ 0x00F4, 0x00D4 }, +	{ 0x00F5, 0x00D5 }, +	{ 0x00F6, 0x00D6 }, +	{ 0x00F8, 0x00D8 }, +	{ 0x00F9, 0x00D9 }, +	{ 0x00FA, 0x00DA }, +	{ 0x00FB, 0x00DB }, +	{ 0x00FC, 0x00DC }, +	{ 0x00FD, 0x00DD }, +	{ 0x00FE, 0x00DE }, +	{ 0x00FF, 0x0178 }, +	{ 0x0101, 0x0100 }, +	{ 0x0103, 0x0102 }, +	{ 0x0105, 0x0104 }, +	{ 0x0107, 0x0106 }, +	{ 0x0109, 0x0108 }, +	{ 0x010B, 0x010A }, +	{ 0x010D, 0x010C }, +	{ 0x010F, 0x010E }, +	{ 0x0111, 0x0110 }, +	{ 0x0113, 0x0112 }, +	{ 0x0115, 0x0114 }, +	{ 0x0117, 0x0116 }, +	{ 0x0119, 0x0118 }, +	{ 0x011B, 0x011A }, +	{ 0x011D, 0x011C }, +	{ 0x011F, 0x011E }, +	{ 0x0121, 0x0120 }, +	{ 0x0123, 0x0122 }, +	{ 0x0125, 0x0124 }, +	{ 0x0127, 0x0126 }, +	{ 0x0129, 0x0128 }, +	{ 0x012B, 0x012A }, +	{ 0x012D, 0x012C }, +	{ 0x012F, 0x012E }, +	{ 0x0131, 0x0049 }, +	{ 0x0133, 0x0132 }, +	{ 0x0135, 0x0134 }, +	{ 0x0137, 0x0136 }, +	{ 0x013A, 0x0139 }, +	{ 0x013C, 0x013B }, +	{ 0x013E, 0x013D }, +	{ 0x0140, 0x013F }, +	{ 0x0142, 0x0141 }, +	{ 0x0144, 0x0143 }, +	{ 0x0146, 0x0145 }, +	{ 0x0148, 0x0147 }, +	{ 0x014B, 0x014A }, +	{ 0x014D, 0x014C }, +	{ 0x014F, 0x014E }, +	{ 0x0151, 0x0150 }, +	{ 0x0153, 0x0152 }, +	{ 0x0155, 0x0154 }, +	{ 0x0157, 0x0156 }, +	{ 0x0159, 0x0158 }, +	{ 0x015B, 0x015A }, +	{ 0x015D, 0x015C }, +	{ 0x015F, 0x015E }, +	{ 0x0161, 0x0160 }, +	{ 0x0163, 0x0162 }, +	{ 0x0165, 0x0164 }, +	{ 0x0167, 0x0166 }, +	{ 0x0169, 0x0168 }, +	{ 0x016B, 0x016A }, +	{ 0x016D, 0x016C }, +	{ 0x016F, 0x016E }, +	{ 0x0171, 0x0170 }, +	{ 0x0173, 0x0172 }, +	{ 0x0175, 0x0174 }, +	{ 0x0177, 0x0176 }, +	{ 0x017A, 0x0179 }, +	{ 0x017C, 0x017B }, +	{ 0x017E, 0x017D }, +	{ 0x0183, 0x0182 }, +	{ 0x0185, 0x0184 }, +	{ 0x0188, 0x0187 }, +	{ 0x018C, 0x018B }, +	{ 0x0192, 0x0191 }, +	{ 0x0199, 0x0198 }, +	{ 0x01A1, 0x01A0 }, +	{ 0x01A3, 0x01A2 }, +	{ 0x01A5, 0x01A4 }, +	{ 0x01A8, 0x01A7 }, +	{ 0x01AD, 0x01AC }, +	{ 0x01B0, 0x01AF }, +	{ 0x01B4, 0x01B3 }, +	{ 0x01B6, 0x01B5 }, +	{ 0x01B9, 0x01B8 }, +	{ 0x01BD, 0x01BC }, +	{ 0x01C6, 0x01C4 }, +	{ 0x01C9, 0x01C7 }, +	{ 0x01CC, 0x01CA }, +	{ 0x01CE, 0x01CD }, +	{ 0x01D0, 0x01CF }, +	{ 0x01D2, 0x01D1 }, +	{ 0x01D4, 0x01D3 }, +	{ 0x01D6, 0x01D5 }, +	{ 0x01D8, 0x01D7 }, +	{ 0x01DA, 0x01D9 }, +	{ 0x01DC, 0x01DB }, +	{ 0x01DF, 0x01DE }, +	{ 0x01E1, 0x01E0 }, +	{ 0x01E3, 0x01E2 }, +	{ 0x01E5, 0x01E4 }, +	{ 0x01E7, 0x01E6 }, +	{ 0x01E9, 0x01E8 }, +	{ 0x01EB, 0x01EA }, +	{ 0x01ED, 0x01EC }, +	{ 0x01EF, 0x01EE }, +	{ 0x01F3, 0x01F1 }, +	{ 0x01F5, 0x01F4 }, +	{ 0x01FB, 0x01FA }, +	{ 0x01FD, 0x01FC }, +	{ 0x01FF, 0x01FE }, +	{ 0x0201, 0x0200 }, +	{ 0x0203, 0x0202 }, +	{ 0x0205, 0x0204 }, +	{ 0x0207, 0x0206 }, +	{ 0x0209, 0x0208 }, +	{ 0x020B, 0x020A }, +	{ 0x020D, 0x020C }, +	{ 0x020F, 0x020E }, +	{ 0x0211, 0x0210 }, +	{ 0x0213, 0x0212 }, +	{ 0x0215, 0x0214 }, +	{ 0x0217, 0x0216 }, +	{ 0x0253, 0x0181 }, +	{ 0x0254, 0x0186 }, +	{ 0x0257, 0x018A }, +	{ 0x0258, 0x018E }, +	{ 0x0259, 0x018F }, +	{ 0x025B, 0x0190 }, +	{ 0x0260, 0x0193 }, +	{ 0x0263, 0x0194 }, +	{ 0x0268, 0x0197 }, +	{ 0x0269, 0x0196 }, +	{ 0x026F, 0x019C }, +	{ 0x0272, 0x019D }, +	{ 0x0275, 0x019F }, +	{ 0x0283, 0x01A9 }, +	{ 0x0288, 0x01AE }, +	{ 0x028A, 0x01B1 }, +	{ 0x028B, 0x01B2 }, +	{ 0x0292, 0x01B7 }, +	{ 0x03AC, 0x0386 }, +	{ 0x03AD, 0x0388 }, +	{ 0x03AE, 0x0389 }, +	{ 0x03AF, 0x038A }, +	{ 0x03B1, 0x0391 }, +	{ 0x03B2, 0x0392 }, +	{ 0x03B3, 0x0393 }, +	{ 0x03B4, 0x0394 }, +	{ 0x03B5, 0x0395 }, +	{ 0x03B6, 0x0396 }, +	{ 0x03B7, 0x0397 }, +	{ 0x03B8, 0x0398 }, +	{ 0x03B9, 0x0399 }, +	{ 0x03BA, 0x039A }, +	{ 0x03BB, 0x039B }, +	{ 0x03BC, 0x039C }, +	{ 0x03BD, 0x039D }, +	{ 0x03BE, 0x039E }, +	{ 0x03BF, 0x039F }, +	{ 0x03C0, 0x03A0 }, +	{ 0x03C1, 0x03A1 }, +	{ 0x03C3, 0x03A3 }, +	{ 0x03C4, 0x03A4 }, +	{ 0x03C5, 0x03A5 }, +	{ 0x03C6, 0x03A6 }, +	{ 0x03C7, 0x03A7 }, +	{ 0x03C8, 0x03A8 }, +	{ 0x03C9, 0x03A9 }, +	{ 0x03CA, 0x03AA }, +	{ 0x03CB, 0x03AB }, +	{ 0x03CC, 0x038C }, +	{ 0x03CD, 0x038E }, +	{ 0x03CE, 0x038F }, +	{ 0x03E3, 0x03E2 }, +	{ 0x03E5, 0x03E4 }, +	{ 0x03E7, 0x03E6 }, +	{ 0x03E9, 0x03E8 }, +	{ 0x03EB, 0x03EA }, +	{ 0x03ED, 0x03EC }, +	{ 0x03EF, 0x03EE }, +	{ 0x0430, 0x0410 }, +	{ 0x0431, 0x0411 }, +	{ 0x0432, 0x0412 }, +	{ 0x0433, 0x0413 }, +	{ 0x0434, 0x0414 }, +	{ 0x0435, 0x0415 }, +	{ 0x0436, 0x0416 }, +	{ 0x0437, 0x0417 }, +	{ 0x0438, 0x0418 }, +	{ 0x0439, 0x0419 }, +	{ 0x043A, 0x041A }, +	{ 0x043B, 0x041B }, +	{ 0x043C, 0x041C }, +	{ 0x043D, 0x041D }, +	{ 0x043E, 0x041E }, +	{ 0x043F, 0x041F }, +	{ 0x0440, 0x0420 }, +	{ 0x0441, 0x0421 }, +	{ 0x0442, 0x0422 }, +	{ 0x0443, 0x0423 }, +	{ 0x0444, 0x0424 }, +	{ 0x0445, 0x0425 }, +	{ 0x0446, 0x0426 }, +	{ 0x0447, 0x0427 }, +	{ 0x0448, 0x0428 }, +	{ 0x0449, 0x0429 }, +	{ 0x044A, 0x042A }, +	{ 0x044B, 0x042B }, +	{ 0x044C, 0x042C }, +	{ 0x044D, 0x042D }, +	{ 0x044E, 0x042E }, +	{ 0x044F, 0x042F }, +	{ 0x0451, 0x0401 }, +	{ 0x0452, 0x0402 }, +	{ 0x0453, 0x0403 }, +	{ 0x0454, 0x0404 }, +	{ 0x0455, 0x0405 }, +	{ 0x0456, 0x0406 }, +	{ 0x0457, 0x0407 }, +	{ 0x0458, 0x0408 }, +	{ 0x0459, 0x0409 }, +	{ 0x045A, 0x040A }, +	{ 0x045B, 0x040B }, +	{ 0x045C, 0x040C }, +	{ 0x045E, 0x040E }, +	{ 0x045F, 0x040F }, +	{ 0x0461, 0x0460 }, +	{ 0x0463, 0x0462 }, +	{ 0x0465, 0x0464 }, +	{ 0x0467, 0x0466 }, +	{ 0x0469, 0x0468 }, +	{ 0x046B, 0x046A }, +	{ 0x046D, 0x046C }, +	{ 0x046F, 0x046E }, +	{ 0x0471, 0x0470 }, +	{ 0x0473, 0x0472 }, +	{ 0x0475, 0x0474 }, +	{ 0x0477, 0x0476 }, +	{ 0x0479, 0x0478 }, +	{ 0x047B, 0x047A }, +	{ 0x047D, 0x047C }, +	{ 0x047F, 0x047E }, +	{ 0x0481, 0x0480 }, +	{ 0x0491, 0x0490 }, +	{ 0x0493, 0x0492 }, +	{ 0x0495, 0x0494 }, +	{ 0x0497, 0x0496 }, +	{ 0x0499, 0x0498 }, +	{ 0x049B, 0x049A }, +	{ 0x049D, 0x049C }, +	{ 0x049F, 0x049E }, +	{ 0x04A1, 0x04A0 }, +	{ 0x04A3, 0x04A2 }, +	{ 0x04A5, 0x04A4 }, +	{ 0x04A7, 0x04A6 }, +	{ 0x04A9, 0x04A8 }, +	{ 0x04AB, 0x04AA }, +	{ 0x04AD, 0x04AC }, +	{ 0x04AF, 0x04AE }, +	{ 0x04B1, 0x04B0 }, +	{ 0x04B3, 0x04B2 }, +	{ 0x04B5, 0x04B4 }, +	{ 0x04B7, 0x04B6 }, +	{ 0x04B9, 0x04B8 }, +	{ 0x04BB, 0x04BA }, +	{ 0x04BD, 0x04BC }, +	{ 0x04BF, 0x04BE }, +	{ 0x04C2, 0x04C1 }, +	{ 0x04C4, 0x04C3 }, +	{ 0x04C8, 0x04C7 }, +	{ 0x04CC, 0x04CB }, +	{ 0x04D1, 0x04D0 }, +	{ 0x04D3, 0x04D2 }, +	{ 0x04D5, 0x04D4 }, +	{ 0x04D7, 0x04D6 }, +	{ 0x04D9, 0x04D8 }, +	{ 0x04DB, 0x04DA }, +	{ 0x04DD, 0x04DC }, +	{ 0x04DF, 0x04DE }, +	{ 0x04E1, 0x04E0 }, +	{ 0x04E3, 0x04E2 }, +	{ 0x04E5, 0x04E4 }, +	{ 0x04E7, 0x04E6 }, +	{ 0x04E9, 0x04E8 }, +	{ 0x04EB, 0x04EA }, +	{ 0x04EF, 0x04EE }, +	{ 0x04F1, 0x04F0 }, +	{ 0x04F3, 0x04F2 }, +	{ 0x04F5, 0x04F4 }, +	{ 0x04F9, 0x04F8 }, +	{ 0x0561, 0x0531 }, +	{ 0x0562, 0x0532 }, +	{ 0x0563, 0x0533 }, +	{ 0x0564, 0x0534 }, +	{ 0x0565, 0x0535 }, +	{ 0x0566, 0x0536 }, +	{ 0x0567, 0x0537 }, +	{ 0x0568, 0x0538 }, +	{ 0x0569, 0x0539 }, +	{ 0x056A, 0x053A }, +	{ 0x056B, 0x053B }, +	{ 0x056C, 0x053C }, +	{ 0x056D, 0x053D }, +	{ 0x056E, 0x053E }, +	{ 0x056F, 0x053F }, +	{ 0x0570, 0x0540 }, +	{ 0x0571, 0x0541 }, +	{ 0x0572, 0x0542 }, +	{ 0x0573, 0x0543 }, +	{ 0x0574, 0x0544 }, +	{ 0x0575, 0x0545 }, +	{ 0x0576, 0x0546 }, +	{ 0x0577, 0x0547 }, +	{ 0x0578, 0x0548 }, +	{ 0x0579, 0x0549 }, +	{ 0x057A, 0x054A }, +	{ 0x057B, 0x054B }, +	{ 0x057C, 0x054C }, +	{ 0x057D, 0x054D }, +	{ 0x057E, 0x054E }, +	{ 0x057F, 0x054F }, +	{ 0x0580, 0x0550 }, +	{ 0x0581, 0x0551 }, +	{ 0x0582, 0x0552 }, +	{ 0x0583, 0x0553 }, +	{ 0x0584, 0x0554 }, +	{ 0x0585, 0x0555 }, +	{ 0x0586, 0x0556 }, +	{ 0x10D0, 0x10A0 }, +	{ 0x10D1, 0x10A1 }, +	{ 0x10D2, 0x10A2 }, +	{ 0x10D3, 0x10A3 }, +	{ 0x10D4, 0x10A4 }, +	{ 0x10D5, 0x10A5 }, +	{ 0x10D6, 0x10A6 }, +	{ 0x10D7, 0x10A7 }, +	{ 0x10D8, 0x10A8 }, +	{ 0x10D9, 0x10A9 }, +	{ 0x10DA, 0x10AA }, +	{ 0x10DB, 0x10AB }, +	{ 0x10DC, 0x10AC }, +	{ 0x10DD, 0x10AD }, +	{ 0x10DE, 0x10AE }, +	{ 0x10DF, 0x10AF }, +	{ 0x10E0, 0x10B0 }, +	{ 0x10E1, 0x10B1 }, +	{ 0x10E2, 0x10B2 }, +	{ 0x10E3, 0x10B3 }, +	{ 0x10E4, 0x10B4 }, +	{ 0x10E5, 0x10B5 }, +	{ 0x10E6, 0x10B6 }, +	{ 0x10E7, 0x10B7 }, +	{ 0x10E8, 0x10B8 }, +	{ 0x10E9, 0x10B9 }, +	{ 0x10EA, 0x10BA }, +	{ 0x10EB, 0x10BB }, +	{ 0x10EC, 0x10BC }, +	{ 0x10ED, 0x10BD }, +	{ 0x10EE, 0x10BE }, +	{ 0x10EF, 0x10BF }, +	{ 0x10F0, 0x10C0 }, +	{ 0x10F1, 0x10C1 }, +	{ 0x10F2, 0x10C2 }, +	{ 0x10F3, 0x10C3 }, +	{ 0x10F4, 0x10C4 }, +	{ 0x10F5, 0x10C5 }, +	{ 0x1E01, 0x1E00 }, +	{ 0x1E03, 0x1E02 }, +	{ 0x1E05, 0x1E04 }, +	{ 0x1E07, 0x1E06 }, +	{ 0x1E09, 0x1E08 }, +	{ 0x1E0B, 0x1E0A }, +	{ 0x1E0D, 0x1E0C }, +	{ 0x1E0F, 0x1E0E }, +	{ 0x1E11, 0x1E10 }, +	{ 0x1E13, 0x1E12 }, +	{ 0x1E15, 0x1E14 }, +	{ 0x1E17, 0x1E16 }, +	{ 0x1E19, 0x1E18 }, +	{ 0x1E1B, 0x1E1A }, +	{ 0x1E1D, 0x1E1C }, +	{ 0x1E1F, 0x1E1E }, +	{ 0x1E21, 0x1E20 }, +	{ 0x1E23, 0x1E22 }, +	{ 0x1E25, 0x1E24 }, +	{ 0x1E27, 0x1E26 }, +	{ 0x1E29, 0x1E28 }, +	{ 0x1E2B, 0x1E2A }, +	{ 0x1E2D, 0x1E2C }, +	{ 0x1E2F, 0x1E2E }, +	{ 0x1E31, 0x1E30 }, +	{ 0x1E33, 0x1E32 }, +	{ 0x1E35, 0x1E34 }, +	{ 0x1E37, 0x1E36 }, +	{ 0x1E39, 0x1E38 }, +	{ 0x1E3B, 0x1E3A }, +	{ 0x1E3D, 0x1E3C }, +	{ 0x1E3F, 0x1E3E }, +	{ 0x1E41, 0x1E40 }, +	{ 0x1E43, 0x1E42 }, +	{ 0x1E45, 0x1E44 }, +	{ 0x1E47, 0x1E46 }, +	{ 0x1E49, 0x1E48 }, +	{ 0x1E4B, 0x1E4A }, +	{ 0x1E4D, 0x1E4C }, +	{ 0x1E4F, 0x1E4E }, +	{ 0x1E51, 0x1E50 }, +	{ 0x1E53, 0x1E52 }, +	{ 0x1E55, 0x1E54 }, +	{ 0x1E57, 0x1E56 }, +	{ 0x1E59, 0x1E58 }, +	{ 0x1E5B, 0x1E5A }, +	{ 0x1E5D, 0x1E5C }, +	{ 0x1E5F, 0x1E5E }, +	{ 0x1E61, 0x1E60 }, +	{ 0x1E63, 0x1E62 }, +	{ 0x1E65, 0x1E64 }, +	{ 0x1E67, 0x1E66 }, +	{ 0x1E69, 0x1E68 }, +	{ 0x1E6B, 0x1E6A }, +	{ 0x1E6D, 0x1E6C }, +	{ 0x1E6F, 0x1E6E }, +	{ 0x1E71, 0x1E70 }, +	{ 0x1E73, 0x1E72 }, +	{ 0x1E75, 0x1E74 }, +	{ 0x1E77, 0x1E76 }, +	{ 0x1E79, 0x1E78 }, +	{ 0x1E7B, 0x1E7A }, +	{ 0x1E7D, 0x1E7C }, +	{ 0x1E7F, 0x1E7E }, +	{ 0x1E81, 0x1E80 }, +	{ 0x1E83, 0x1E82 }, +	{ 0x1E85, 0x1E84 }, +	{ 0x1E87, 0x1E86 }, +	{ 0x1E89, 0x1E88 }, +	{ 0x1E8B, 0x1E8A }, +	{ 0x1E8D, 0x1E8C }, +	{ 0x1E8F, 0x1E8E }, +	{ 0x1E91, 0x1E90 }, +	{ 0x1E93, 0x1E92 }, +	{ 0x1E95, 0x1E94 }, +	{ 0x1EA1, 0x1EA0 }, +	{ 0x1EA3, 0x1EA2 }, +	{ 0x1EA5, 0x1EA4 }, +	{ 0x1EA7, 0x1EA6 }, +	{ 0x1EA9, 0x1EA8 }, +	{ 0x1EAB, 0x1EAA }, +	{ 0x1EAD, 0x1EAC }, +	{ 0x1EAF, 0x1EAE }, +	{ 0x1EB1, 0x1EB0 }, +	{ 0x1EB3, 0x1EB2 }, +	{ 0x1EB5, 0x1EB4 }, +	{ 0x1EB7, 0x1EB6 }, +	{ 0x1EB9, 0x1EB8 }, +	{ 0x1EBB, 0x1EBA }, +	{ 0x1EBD, 0x1EBC }, +	{ 0x1EBF, 0x1EBE }, +	{ 0x1EC1, 0x1EC0 }, +	{ 0x1EC3, 0x1EC2 }, +	{ 0x1EC5, 0x1EC4 }, +	{ 0x1EC7, 0x1EC6 }, +	{ 0x1EC9, 0x1EC8 }, +	{ 0x1ECB, 0x1ECA }, +	{ 0x1ECD, 0x1ECC }, +	{ 0x1ECF, 0x1ECE }, +	{ 0x1ED1, 0x1ED0 }, +	{ 0x1ED3, 0x1ED2 }, +	{ 0x1ED5, 0x1ED4 }, +	{ 0x1ED7, 0x1ED6 }, +	{ 0x1ED9, 0x1ED8 }, +	{ 0x1EDB, 0x1EDA }, +	{ 0x1EDD, 0x1EDC }, +	{ 0x1EDF, 0x1EDE }, +	{ 0x1EE1, 0x1EE0 }, +	{ 0x1EE3, 0x1EE2 }, +	{ 0x1EE5, 0x1EE4 }, +	{ 0x1EE7, 0x1EE6 }, +	{ 0x1EE9, 0x1EE8 }, +	{ 0x1EEB, 0x1EEA }, +	{ 0x1EED, 0x1EEC }, +	{ 0x1EEF, 0x1EEE }, +	{ 0x1EF1, 0x1EF0 }, +	{ 0x1EF3, 0x1EF2 }, +	{ 0x1EF5, 0x1EF4 }, +	{ 0x1EF7, 0x1EF6 }, +	{ 0x1EF9, 0x1EF8 }, +	{ 0x1F00, 0x1F08 }, +	{ 0x1F01, 0x1F09 }, +	{ 0x1F02, 0x1F0A }, +	{ 0x1F03, 0x1F0B }, +	{ 0x1F04, 0x1F0C }, +	{ 0x1F05, 0x1F0D }, +	{ 0x1F06, 0x1F0E }, +	{ 0x1F07, 0x1F0F }, +	{ 0x1F10, 0x1F18 }, +	{ 0x1F11, 0x1F19 }, +	{ 0x1F12, 0x1F1A }, +	{ 0x1F13, 0x1F1B }, +	{ 0x1F14, 0x1F1C }, +	{ 0x1F15, 0x1F1D }, +	{ 0x1F20, 0x1F28 }, +	{ 0x1F21, 0x1F29 }, +	{ 0x1F22, 0x1F2A }, +	{ 0x1F23, 0x1F2B }, +	{ 0x1F24, 0x1F2C }, +	{ 0x1F25, 0x1F2D }, +	{ 0x1F26, 0x1F2E }, +	{ 0x1F27, 0x1F2F }, +	{ 0x1F30, 0x1F38 }, +	{ 0x1F31, 0x1F39 }, +	{ 0x1F32, 0x1F3A }, +	{ 0x1F33, 0x1F3B }, +	{ 0x1F34, 0x1F3C }, +	{ 0x1F35, 0x1F3D }, +	{ 0x1F36, 0x1F3E }, +	{ 0x1F37, 0x1F3F }, +	{ 0x1F40, 0x1F48 }, +	{ 0x1F41, 0x1F49 }, +	{ 0x1F42, 0x1F4A }, +	{ 0x1F43, 0x1F4B }, +	{ 0x1F44, 0x1F4C }, +	{ 0x1F45, 0x1F4D }, +	{ 0x1F51, 0x1F59 }, +	{ 0x1F53, 0x1F5B }, +	{ 0x1F55, 0x1F5D }, +	{ 0x1F57, 0x1F5F }, +	{ 0x1F60, 0x1F68 }, +	{ 0x1F61, 0x1F69 }, +	{ 0x1F62, 0x1F6A }, +	{ 0x1F63, 0x1F6B }, +	{ 0x1F64, 0x1F6C }, +	{ 0x1F65, 0x1F6D }, +	{ 0x1F66, 0x1F6E }, +	{ 0x1F67, 0x1F6F }, +	{ 0x1F80, 0x1F88 }, +	{ 0x1F81, 0x1F89 }, +	{ 0x1F82, 0x1F8A }, +	{ 0x1F83, 0x1F8B }, +	{ 0x1F84, 0x1F8C }, +	{ 0x1F85, 0x1F8D }, +	{ 0x1F86, 0x1F8E }, +	{ 0x1F87, 0x1F8F }, +	{ 0x1F90, 0x1F98 }, +	{ 0x1F91, 0x1F99 }, +	{ 0x1F92, 0x1F9A }, +	{ 0x1F93, 0x1F9B }, +	{ 0x1F94, 0x1F9C }, +	{ 0x1F95, 0x1F9D }, +	{ 0x1F96, 0x1F9E }, +	{ 0x1F97, 0x1F9F }, +	{ 0x1FA0, 0x1FA8 }, +	{ 0x1FA1, 0x1FA9 }, +	{ 0x1FA2, 0x1FAA }, +	{ 0x1FA3, 0x1FAB }, +	{ 0x1FA4, 0x1FAC }, +	{ 0x1FA5, 0x1FAD }, +	{ 0x1FA6, 0x1FAE }, +	{ 0x1FA7, 0x1FAF }, +	{ 0x1FB0, 0x1FB8 }, +	{ 0x1FB1, 0x1FB9 }, +	{ 0x1FD0, 0x1FD8 }, +	{ 0x1FD1, 0x1FD9 }, +	{ 0x1FE0, 0x1FE8 }, +	{ 0x1FE1, 0x1FE9 }, +	{ 0x24D0, 0x24B6 }, +	{ 0x24D1, 0x24B7 }, +	{ 0x24D2, 0x24B8 }, +	{ 0x24D3, 0x24B9 }, +	{ 0x24D4, 0x24BA }, +	{ 0x24D5, 0x24BB }, +	{ 0x24D6, 0x24BC }, +	{ 0x24D7, 0x24BD }, +	{ 0x24D8, 0x24BE }, +	{ 0x24D9, 0x24BF }, +	{ 0x24DA, 0x24C0 }, +	{ 0x24DB, 0x24C1 }, +	{ 0x24DC, 0x24C2 }, +	{ 0x24DD, 0x24C3 }, +	{ 0x24DE, 0x24C4 }, +	{ 0x24DF, 0x24C5 }, +	{ 0x24E0, 0x24C6 }, +	{ 0x24E1, 0x24C7 }, +	{ 0x24E2, 0x24C8 }, +	{ 0x24E3, 0x24C9 }, +	{ 0x24E4, 0x24CA }, +	{ 0x24E5, 0x24CB }, +	{ 0x24E6, 0x24CC }, +	{ 0x24E7, 0x24CD }, +	{ 0x24E8, 0x24CE }, +	{ 0x24E9, 0x24CF }, +	{ 0xFF41, 0xFF21 }, +	{ 0xFF42, 0xFF22 }, +	{ 0xFF43, 0xFF23 }, +	{ 0xFF44, 0xFF24 }, +	{ 0xFF45, 0xFF25 }, +	{ 0xFF46, 0xFF26 }, +	{ 0xFF47, 0xFF27 }, +	{ 0xFF48, 0xFF28 }, +	{ 0xFF49, 0xFF29 }, +	{ 0xFF4A, 0xFF2A }, +	{ 0xFF4B, 0xFF2B }, +	{ 0xFF4C, 0xFF2C }, +	{ 0xFF4D, 0xFF2D }, +	{ 0xFF4E, 0xFF2E }, +	{ 0xFF4F, 0xFF2F }, +	{ 0xFF50, 0xFF30 }, +	{ 0xFF51, 0xFF31 }, +	{ 0xFF52, 0xFF32 }, +	{ 0xFF53, 0xFF33 }, +	{ 0xFF54, 0xFF34 }, +	{ 0xFF55, 0xFF35 }, +	{ 0xFF56, 0xFF36 }, +	{ 0xFF57, 0xFF37 }, +	{ 0xFF58, 0xFF38 }, +	{ 0xFF59, 0xFF39 }, +	{ 0xFF5A, 0xFF3A }, +}; + +static const int reverse_caps_table[CAPS_LEN - 1][2] = { +	{ 0x0041, 0x0061 }, +	{ 0x0042, 0x0062 }, +	{ 0x0043, 0x0063 }, +	{ 0x0044, 0x0064 }, +	{ 0x0045, 0x0065 }, +	{ 0x0046, 0x0066 }, +	{ 0x0047, 0x0067 }, +	{ 0x0048, 0x0068 }, +	{ 0x0049, 0x0069 }, +	// { 0x0049, 0x0131 }, // dotless I +	{ 0x004A, 0x006A }, +	{ 0x004B, 0x006B }, +	{ 0x004C, 0x006C }, +	{ 0x004D, 0x006D }, +	{ 0x004E, 0x006E }, +	{ 0x004F, 0x006F }, +	{ 0x0050, 0x0070 }, +	{ 0x0051, 0x0071 }, +	{ 0x0052, 0x0072 }, +	{ 0x0053, 0x0073 }, +	{ 0x0054, 0x0074 }, +	{ 0x0055, 0x0075 }, +	{ 0x0056, 0x0076 }, +	{ 0x0057, 0x0077 }, +	{ 0x0058, 0x0078 }, +	{ 0x0059, 0x0079 }, +	{ 0x005A, 0x007A }, +	{ 0x00C0, 0x00E0 }, +	{ 0x00C1, 0x00E1 }, +	{ 0x00C2, 0x00E2 }, +	{ 0x00C3, 0x00E3 }, +	{ 0x00C4, 0x00E4 }, +	{ 0x00C5, 0x00E5 }, +	{ 0x00C6, 0x00E6 }, +	{ 0x00C7, 0x00E7 }, +	{ 0x00C8, 0x00E8 }, +	{ 0x00C9, 0x00E9 }, +	{ 0x00CA, 0x00EA }, +	{ 0x00CB, 0x00EB }, +	{ 0x00CC, 0x00EC }, +	{ 0x00CD, 0x00ED }, +	{ 0x00CE, 0x00EE }, +	{ 0x00CF, 0x00EF }, +	{ 0x00D0, 0x00F0 }, +	{ 0x00D1, 0x00F1 }, +	{ 0x00D2, 0x00F2 }, +	{ 0x00D3, 0x00F3 }, +	{ 0x00D4, 0x00F4 }, +	{ 0x00D5, 0x00F5 }, +	{ 0x00D6, 0x00F6 }, +	{ 0x00D8, 0x00F8 }, +	{ 0x00D9, 0x00F9 }, +	{ 0x00DA, 0x00FA }, +	{ 0x00DB, 0x00FB }, +	{ 0x00DC, 0x00FC }, +	{ 0x00DD, 0x00FD }, +	{ 0x00DE, 0x00FE }, +	{ 0x0100, 0x0101 }, +	{ 0x0102, 0x0103 }, +	{ 0x0104, 0x0105 }, +	{ 0x0106, 0x0107 }, +	{ 0x0108, 0x0109 }, +	{ 0x010A, 0x010B }, +	{ 0x010C, 0x010D }, +	{ 0x010E, 0x010F }, +	{ 0x0110, 0x0111 }, +	{ 0x0112, 0x0113 }, +	{ 0x0114, 0x0115 }, +	{ 0x0116, 0x0117 }, +	{ 0x0118, 0x0119 }, +	{ 0x011A, 0x011B }, +	{ 0x011C, 0x011D }, +	{ 0x011E, 0x011F }, +	{ 0x0120, 0x0121 }, +	{ 0x0122, 0x0123 }, +	{ 0x0124, 0x0125 }, +	{ 0x0126, 0x0127 }, +	{ 0x0128, 0x0129 }, +	{ 0x012A, 0x012B }, +	{ 0x012C, 0x012D }, +	{ 0x012E, 0x012F }, +	{ 0x0132, 0x0133 }, +	{ 0x0134, 0x0135 }, +	{ 0x0136, 0x0137 }, +	{ 0x0139, 0x013A }, +	{ 0x013B, 0x013C }, +	{ 0x013D, 0x013E }, +	{ 0x013F, 0x0140 }, +	{ 0x0141, 0x0142 }, +	{ 0x0143, 0x0144 }, +	{ 0x0145, 0x0146 }, +	{ 0x0147, 0x0148 }, +	{ 0x014A, 0x014B }, +	{ 0x014C, 0x014D }, +	{ 0x014E, 0x014F }, +	{ 0x0150, 0x0151 }, +	{ 0x0152, 0x0153 }, +	{ 0x0154, 0x0155 }, +	{ 0x0156, 0x0157 }, +	{ 0x0158, 0x0159 }, +	{ 0x015A, 0x015B }, +	{ 0x015C, 0x015D }, +	{ 0x015E, 0x015F }, +	{ 0x0160, 0x0161 }, +	{ 0x0162, 0x0163 }, +	{ 0x0164, 0x0165 }, +	{ 0x0166, 0x0167 }, +	{ 0x0168, 0x0169 }, +	{ 0x016A, 0x016B }, +	{ 0x016C, 0x016D }, +	{ 0x016E, 0x016F }, +	{ 0x0170, 0x0171 }, +	{ 0x0172, 0x0173 }, +	{ 0x0174, 0x0175 }, +	{ 0x0176, 0x0177 }, +	{ 0x0178, 0x00FF }, +	{ 0x0179, 0x017A }, +	{ 0x017B, 0x017C }, +	{ 0x017D, 0x017E }, +	{ 0x0181, 0x0253 }, +	{ 0x0182, 0x0183 }, +	{ 0x0184, 0x0185 }, +	{ 0x0186, 0x0254 }, +	{ 0x0187, 0x0188 }, +	{ 0x018A, 0x0257 }, +	{ 0x018B, 0x018C }, +	{ 0x018E, 0x0258 }, +	{ 0x018F, 0x0259 }, +	{ 0x0190, 0x025B }, +	{ 0x0191, 0x0192 }, +	{ 0x0193, 0x0260 }, +	{ 0x0194, 0x0263 }, +	{ 0x0196, 0x0269 }, +	{ 0x0197, 0x0268 }, +	{ 0x0198, 0x0199 }, +	{ 0x019C, 0x026F }, +	{ 0x019D, 0x0272 }, +	{ 0x019F, 0x0275 }, +	{ 0x01A0, 0x01A1 }, +	{ 0x01A2, 0x01A3 }, +	{ 0x01A4, 0x01A5 }, +	{ 0x01A7, 0x01A8 }, +	{ 0x01A9, 0x0283 }, +	{ 0x01AC, 0x01AD }, +	{ 0x01AE, 0x0288 }, +	{ 0x01AF, 0x01B0 }, +	{ 0x01B1, 0x028A }, +	{ 0x01B2, 0x028B }, +	{ 0x01B3, 0x01B4 }, +	{ 0x01B5, 0x01B6 }, +	{ 0x01B7, 0x0292 }, +	{ 0x01B8, 0x01B9 }, +	{ 0x01BC, 0x01BD }, +	{ 0x01C4, 0x01C6 }, +	{ 0x01C7, 0x01C9 }, +	{ 0x01CA, 0x01CC }, +	{ 0x01CD, 0x01CE }, +	{ 0x01CF, 0x01D0 }, +	{ 0x01D1, 0x01D2 }, +	{ 0x01D3, 0x01D4 }, +	{ 0x01D5, 0x01D6 }, +	{ 0x01D7, 0x01D8 }, +	{ 0x01D9, 0x01DA }, +	{ 0x01DB, 0x01DC }, +	{ 0x01DE, 0x01DF }, +	{ 0x01E0, 0x01E1 }, +	{ 0x01E2, 0x01E3 }, +	{ 0x01E4, 0x01E5 }, +	{ 0x01E6, 0x01E7 }, +	{ 0x01E8, 0x01E9 }, +	{ 0x01EA, 0x01EB }, +	{ 0x01EC, 0x01ED }, +	{ 0x01EE, 0x01EF }, +	{ 0x01F1, 0x01F3 }, +	{ 0x01F4, 0x01F5 }, +	{ 0x01FA, 0x01FB }, +	{ 0x01FC, 0x01FD }, +	{ 0x01FE, 0x01FF }, +	{ 0x0200, 0x0201 }, +	{ 0x0202, 0x0203 }, +	{ 0x0204, 0x0205 }, +	{ 0x0206, 0x0207 }, +	{ 0x0208, 0x0209 }, +	{ 0x020A, 0x020B }, +	{ 0x020C, 0x020D }, +	{ 0x020E, 0x020F }, +	{ 0x0210, 0x0211 }, +	{ 0x0212, 0x0213 }, +	{ 0x0214, 0x0215 }, +	{ 0x0216, 0x0217 }, +	{ 0x0386, 0x03AC }, +	{ 0x0388, 0x03AD }, +	{ 0x0389, 0x03AE }, +	{ 0x038A, 0x03AF }, +	{ 0x038C, 0x03CC }, +	{ 0x038E, 0x03CD }, +	{ 0x038F, 0x03CE }, +	{ 0x0391, 0x03B1 }, +	{ 0x0392, 0x03B2 }, +	{ 0x0393, 0x03B3 }, +	{ 0x0394, 0x03B4 }, +	{ 0x0395, 0x03B5 }, +	{ 0x0396, 0x03B6 }, +	{ 0x0397, 0x03B7 }, +	{ 0x0398, 0x03B8 }, +	{ 0x0399, 0x03B9 }, +	{ 0x039A, 0x03BA }, +	{ 0x039B, 0x03BB }, +	{ 0x039C, 0x03BC }, +	{ 0x039D, 0x03BD }, +	{ 0x039E, 0x03BE }, +	{ 0x039F, 0x03BF }, +	{ 0x03A0, 0x03C0 }, +	{ 0x03A1, 0x03C1 }, +	{ 0x03A3, 0x03C3 }, +	{ 0x03A4, 0x03C4 }, +	{ 0x03A5, 0x03C5 }, +	{ 0x03A6, 0x03C6 }, +	{ 0x03A7, 0x03C7 }, +	{ 0x03A8, 0x03C8 }, +	{ 0x03A9, 0x03C9 }, +	{ 0x03AA, 0x03CA }, +	{ 0x03AB, 0x03CB }, +	{ 0x03E2, 0x03E3 }, +	{ 0x03E4, 0x03E5 }, +	{ 0x03E6, 0x03E7 }, +	{ 0x03E8, 0x03E9 }, +	{ 0x03EA, 0x03EB }, +	{ 0x03EC, 0x03ED }, +	{ 0x03EE, 0x03EF }, +	{ 0x0401, 0x0451 }, +	{ 0x0402, 0x0452 }, +	{ 0x0403, 0x0453 }, +	{ 0x0404, 0x0454 }, +	{ 0x0405, 0x0455 }, +	{ 0x0406, 0x0456 }, +	{ 0x0407, 0x0457 }, +	{ 0x0408, 0x0458 }, +	{ 0x0409, 0x0459 }, +	{ 0x040A, 0x045A }, +	{ 0x040B, 0x045B }, +	{ 0x040C, 0x045C }, +	{ 0x040E, 0x045E }, +	{ 0x040F, 0x045F }, +	{ 0x0410, 0x0430 }, +	{ 0x0411, 0x0431 }, +	{ 0x0412, 0x0432 }, +	{ 0x0413, 0x0433 }, +	{ 0x0414, 0x0434 }, +	{ 0x0415, 0x0435 }, +	{ 0x0416, 0x0436 }, +	{ 0x0417, 0x0437 }, +	{ 0x0418, 0x0438 }, +	{ 0x0419, 0x0439 }, +	{ 0x041A, 0x043A }, +	{ 0x041B, 0x043B }, +	{ 0x041C, 0x043C }, +	{ 0x041D, 0x043D }, +	{ 0x041E, 0x043E }, +	{ 0x041F, 0x043F }, +	{ 0x0420, 0x0440 }, +	{ 0x0421, 0x0441 }, +	{ 0x0422, 0x0442 }, +	{ 0x0423, 0x0443 }, +	{ 0x0424, 0x0444 }, +	{ 0x0425, 0x0445 }, +	{ 0x0426, 0x0446 }, +	{ 0x0427, 0x0447 }, +	{ 0x0428, 0x0448 }, +	{ 0x0429, 0x0449 }, +	{ 0x042A, 0x044A }, +	{ 0x042B, 0x044B }, +	{ 0x042C, 0x044C }, +	{ 0x042D, 0x044D }, +	{ 0x042E, 0x044E }, +	{ 0x042F, 0x044F }, +	{ 0x0460, 0x0461 }, +	{ 0x0462, 0x0463 }, +	{ 0x0464, 0x0465 }, +	{ 0x0466, 0x0467 }, +	{ 0x0468, 0x0469 }, +	{ 0x046A, 0x046B }, +	{ 0x046C, 0x046D }, +	{ 0x046E, 0x046F }, +	{ 0x0470, 0x0471 }, +	{ 0x0472, 0x0473 }, +	{ 0x0474, 0x0475 }, +	{ 0x0476, 0x0477 }, +	{ 0x0478, 0x0479 }, +	{ 0x047A, 0x047B }, +	{ 0x047C, 0x047D }, +	{ 0x047E, 0x047F }, +	{ 0x0480, 0x0481 }, +	{ 0x0490, 0x0491 }, +	{ 0x0492, 0x0493 }, +	{ 0x0494, 0x0495 }, +	{ 0x0496, 0x0497 }, +	{ 0x0498, 0x0499 }, +	{ 0x049A, 0x049B }, +	{ 0x049C, 0x049D }, +	{ 0x049E, 0x049F }, +	{ 0x04A0, 0x04A1 }, +	{ 0x04A2, 0x04A3 }, +	{ 0x04A4, 0x04A5 }, +	{ 0x04A6, 0x04A7 }, +	{ 0x04A8, 0x04A9 }, +	{ 0x04AA, 0x04AB }, +	{ 0x04AC, 0x04AD }, +	{ 0x04AE, 0x04AF }, +	{ 0x04B0, 0x04B1 }, +	{ 0x04B2, 0x04B3 }, +	{ 0x04B4, 0x04B5 }, +	{ 0x04B6, 0x04B7 }, +	{ 0x04B8, 0x04B9 }, +	{ 0x04BA, 0x04BB }, +	{ 0x04BC, 0x04BD }, +	{ 0x04BE, 0x04BF }, +	{ 0x04C1, 0x04C2 }, +	{ 0x04C3, 0x04C4 }, +	{ 0x04C7, 0x04C8 }, +	{ 0x04CB, 0x04CC }, +	{ 0x04D0, 0x04D1 }, +	{ 0x04D2, 0x04D3 }, +	{ 0x04D4, 0x04D5 }, +	{ 0x04D6, 0x04D7 }, +	{ 0x04D8, 0x04D9 }, +	{ 0x04DA, 0x04DB }, +	{ 0x04DC, 0x04DD }, +	{ 0x04DE, 0x04DF }, +	{ 0x04E0, 0x04E1 }, +	{ 0x04E2, 0x04E3 }, +	{ 0x04E4, 0x04E5 }, +	{ 0x04E6, 0x04E7 }, +	{ 0x04E8, 0x04E9 }, +	{ 0x04EA, 0x04EB }, +	{ 0x04EE, 0x04EF }, +	{ 0x04F0, 0x04F1 }, +	{ 0x04F2, 0x04F3 }, +	{ 0x04F4, 0x04F5 }, +	{ 0x04F8, 0x04F9 }, +	{ 0x0531, 0x0561 }, +	{ 0x0532, 0x0562 }, +	{ 0x0533, 0x0563 }, +	{ 0x0534, 0x0564 }, +	{ 0x0535, 0x0565 }, +	{ 0x0536, 0x0566 }, +	{ 0x0537, 0x0567 }, +	{ 0x0538, 0x0568 }, +	{ 0x0539, 0x0569 }, +	{ 0x053A, 0x056A }, +	{ 0x053B, 0x056B }, +	{ 0x053C, 0x056C }, +	{ 0x053D, 0x056D }, +	{ 0x053E, 0x056E }, +	{ 0x053F, 0x056F }, +	{ 0x0540, 0x0570 }, +	{ 0x0541, 0x0571 }, +	{ 0x0542, 0x0572 }, +	{ 0x0543, 0x0573 }, +	{ 0x0544, 0x0574 }, +	{ 0x0545, 0x0575 }, +	{ 0x0546, 0x0576 }, +	{ 0x0547, 0x0577 }, +	{ 0x0548, 0x0578 }, +	{ 0x0549, 0x0579 }, +	{ 0x054A, 0x057A }, +	{ 0x054B, 0x057B }, +	{ 0x054C, 0x057C }, +	{ 0x054D, 0x057D }, +	{ 0x054E, 0x057E }, +	{ 0x054F, 0x057F }, +	{ 0x0550, 0x0580 }, +	{ 0x0551, 0x0581 }, +	{ 0x0552, 0x0582 }, +	{ 0x0553, 0x0583 }, +	{ 0x0554, 0x0584 }, +	{ 0x0555, 0x0585 }, +	{ 0x0556, 0x0586 }, +	{ 0x10A0, 0x10D0 }, +	{ 0x10A1, 0x10D1 }, +	{ 0x10A2, 0x10D2 }, +	{ 0x10A3, 0x10D3 }, +	{ 0x10A4, 0x10D4 }, +	{ 0x10A5, 0x10D5 }, +	{ 0x10A6, 0x10D6 }, +	{ 0x10A7, 0x10D7 }, +	{ 0x10A8, 0x10D8 }, +	{ 0x10A9, 0x10D9 }, +	{ 0x10AA, 0x10DA }, +	{ 0x10AB, 0x10DB }, +	{ 0x10AC, 0x10DC }, +	{ 0x10AD, 0x10DD }, +	{ 0x10AE, 0x10DE }, +	{ 0x10AF, 0x10DF }, +	{ 0x10B0, 0x10E0 }, +	{ 0x10B1, 0x10E1 }, +	{ 0x10B2, 0x10E2 }, +	{ 0x10B3, 0x10E3 }, +	{ 0x10B4, 0x10E4 }, +	{ 0x10B5, 0x10E5 }, +	{ 0x10B6, 0x10E6 }, +	{ 0x10B7, 0x10E7 }, +	{ 0x10B8, 0x10E8 }, +	{ 0x10B9, 0x10E9 }, +	{ 0x10BA, 0x10EA }, +	{ 0x10BB, 0x10EB }, +	{ 0x10BC, 0x10EC }, +	{ 0x10BD, 0x10ED }, +	{ 0x10BE, 0x10EE }, +	{ 0x10BF, 0x10EF }, +	{ 0x10C0, 0x10F0 }, +	{ 0x10C1, 0x10F1 }, +	{ 0x10C2, 0x10F2 }, +	{ 0x10C3, 0x10F3 }, +	{ 0x10C4, 0x10F4 }, +	{ 0x10C5, 0x10F5 }, +	{ 0x1E00, 0x1E01 }, +	{ 0x1E02, 0x1E03 }, +	{ 0x1E04, 0x1E05 }, +	{ 0x1E06, 0x1E07 }, +	{ 0x1E08, 0x1E09 }, +	{ 0x1E0A, 0x1E0B }, +	{ 0x1E0C, 0x1E0D }, +	{ 0x1E0E, 0x1E0F }, +	{ 0x1E10, 0x1E11 }, +	{ 0x1E12, 0x1E13 }, +	{ 0x1E14, 0x1E15 }, +	{ 0x1E16, 0x1E17 }, +	{ 0x1E18, 0x1E19 }, +	{ 0x1E1A, 0x1E1B }, +	{ 0x1E1C, 0x1E1D }, +	{ 0x1E1E, 0x1E1F }, +	{ 0x1E20, 0x1E21 }, +	{ 0x1E22, 0x1E23 }, +	{ 0x1E24, 0x1E25 }, +	{ 0x1E26, 0x1E27 }, +	{ 0x1E28, 0x1E29 }, +	{ 0x1E2A, 0x1E2B }, +	{ 0x1E2C, 0x1E2D }, +	{ 0x1E2E, 0x1E2F }, +	{ 0x1E30, 0x1E31 }, +	{ 0x1E32, 0x1E33 }, +	{ 0x1E34, 0x1E35 }, +	{ 0x1E36, 0x1E37 }, +	{ 0x1E38, 0x1E39 }, +	{ 0x1E3A, 0x1E3B }, +	{ 0x1E3C, 0x1E3D }, +	{ 0x1E3E, 0x1E3F }, +	{ 0x1E40, 0x1E41 }, +	{ 0x1E42, 0x1E43 }, +	{ 0x1E44, 0x1E45 }, +	{ 0x1E46, 0x1E47 }, +	{ 0x1E48, 0x1E49 }, +	{ 0x1E4A, 0x1E4B }, +	{ 0x1E4C, 0x1E4D }, +	{ 0x1E4E, 0x1E4F }, +	{ 0x1E50, 0x1E51 }, +	{ 0x1E52, 0x1E53 }, +	{ 0x1E54, 0x1E55 }, +	{ 0x1E56, 0x1E57 }, +	{ 0x1E58, 0x1E59 }, +	{ 0x1E5A, 0x1E5B }, +	{ 0x1E5C, 0x1E5D }, +	{ 0x1E5E, 0x1E5F }, +	{ 0x1E60, 0x1E61 }, +	{ 0x1E62, 0x1E63 }, +	{ 0x1E64, 0x1E65 }, +	{ 0x1E66, 0x1E67 }, +	{ 0x1E68, 0x1E69 }, +	{ 0x1E6A, 0x1E6B }, +	{ 0x1E6C, 0x1E6D }, +	{ 0x1E6E, 0x1E6F }, +	{ 0x1E70, 0x1E71 }, +	{ 0x1E72, 0x1E73 }, +	{ 0x1E74, 0x1E75 }, +	{ 0x1E76, 0x1E77 }, +	{ 0x1E78, 0x1E79 }, +	{ 0x1E7A, 0x1E7B }, +	{ 0x1E7C, 0x1E7D }, +	{ 0x1E7E, 0x1E7F }, +	{ 0x1E80, 0x1E81 }, +	{ 0x1E82, 0x1E83 }, +	{ 0x1E84, 0x1E85 }, +	{ 0x1E86, 0x1E87 }, +	{ 0x1E88, 0x1E89 }, +	{ 0x1E8A, 0x1E8B }, +	{ 0x1E8C, 0x1E8D }, +	{ 0x1E8E, 0x1E8F }, +	{ 0x1E90, 0x1E91 }, +	{ 0x1E92, 0x1E93 }, +	{ 0x1E94, 0x1E95 }, +	{ 0x1EA0, 0x1EA1 }, +	{ 0x1EA2, 0x1EA3 }, +	{ 0x1EA4, 0x1EA5 }, +	{ 0x1EA6, 0x1EA7 }, +	{ 0x1EA8, 0x1EA9 }, +	{ 0x1EAA, 0x1EAB }, +	{ 0x1EAC, 0x1EAD }, +	{ 0x1EAE, 0x1EAF }, +	{ 0x1EB0, 0x1EB1 }, +	{ 0x1EB2, 0x1EB3 }, +	{ 0x1EB4, 0x1EB5 }, +	{ 0x1EB6, 0x1EB7 }, +	{ 0x1EB8, 0x1EB9 }, +	{ 0x1EBA, 0x1EBB }, +	{ 0x1EBC, 0x1EBD }, +	{ 0x1EBE, 0x1EBF }, +	{ 0x1EC0, 0x1EC1 }, +	{ 0x1EC2, 0x1EC3 }, +	{ 0x1EC4, 0x1EC5 }, +	{ 0x1EC6, 0x1EC7 }, +	{ 0x1EC8, 0x1EC9 }, +	{ 0x1ECA, 0x1ECB }, +	{ 0x1ECC, 0x1ECD }, +	{ 0x1ECE, 0x1ECF }, +	{ 0x1ED0, 0x1ED1 }, +	{ 0x1ED2, 0x1ED3 }, +	{ 0x1ED4, 0x1ED5 }, +	{ 0x1ED6, 0x1ED7 }, +	{ 0x1ED8, 0x1ED9 }, +	{ 0x1EDA, 0x1EDB }, +	{ 0x1EDC, 0x1EDD }, +	{ 0x1EDE, 0x1EDF }, +	{ 0x1EE0, 0x1EE1 }, +	{ 0x1EE2, 0x1EE3 }, +	{ 0x1EE4, 0x1EE5 }, +	{ 0x1EE6, 0x1EE7 }, +	{ 0x1EE8, 0x1EE9 }, +	{ 0x1EEA, 0x1EEB }, +	{ 0x1EEC, 0x1EED }, +	{ 0x1EEE, 0x1EEF }, +	{ 0x1EF0, 0x1EF1 }, +	{ 0x1EF2, 0x1EF3 }, +	{ 0x1EF4, 0x1EF5 }, +	{ 0x1EF6, 0x1EF7 }, +	{ 0x1EF8, 0x1EF9 }, +	{ 0x1F08, 0x1F00 }, +	{ 0x1F09, 0x1F01 }, +	{ 0x1F0A, 0x1F02 }, +	{ 0x1F0B, 0x1F03 }, +	{ 0x1F0C, 0x1F04 }, +	{ 0x1F0D, 0x1F05 }, +	{ 0x1F0E, 0x1F06 }, +	{ 0x1F0F, 0x1F07 }, +	{ 0x1F18, 0x1F10 }, +	{ 0x1F19, 0x1F11 }, +	{ 0x1F1A, 0x1F12 }, +	{ 0x1F1B, 0x1F13 }, +	{ 0x1F1C, 0x1F14 }, +	{ 0x1F1D, 0x1F15 }, +	{ 0x1F28, 0x1F20 }, +	{ 0x1F29, 0x1F21 }, +	{ 0x1F2A, 0x1F22 }, +	{ 0x1F2B, 0x1F23 }, +	{ 0x1F2C, 0x1F24 }, +	{ 0x1F2D, 0x1F25 }, +	{ 0x1F2E, 0x1F26 }, +	{ 0x1F2F, 0x1F27 }, +	{ 0x1F38, 0x1F30 }, +	{ 0x1F39, 0x1F31 }, +	{ 0x1F3A, 0x1F32 }, +	{ 0x1F3B, 0x1F33 }, +	{ 0x1F3C, 0x1F34 }, +	{ 0x1F3D, 0x1F35 }, +	{ 0x1F3E, 0x1F36 }, +	{ 0x1F3F, 0x1F37 }, +	{ 0x1F48, 0x1F40 }, +	{ 0x1F49, 0x1F41 }, +	{ 0x1F4A, 0x1F42 }, +	{ 0x1F4B, 0x1F43 }, +	{ 0x1F4C, 0x1F44 }, +	{ 0x1F4D, 0x1F45 }, +	{ 0x1F59, 0x1F51 }, +	{ 0x1F5B, 0x1F53 }, +	{ 0x1F5D, 0x1F55 }, +	{ 0x1F5F, 0x1F57 }, +	{ 0x1F68, 0x1F60 }, +	{ 0x1F69, 0x1F61 }, +	{ 0x1F6A, 0x1F62 }, +	{ 0x1F6B, 0x1F63 }, +	{ 0x1F6C, 0x1F64 }, +	{ 0x1F6D, 0x1F65 }, +	{ 0x1F6E, 0x1F66 }, +	{ 0x1F6F, 0x1F67 }, +	{ 0x1F88, 0x1F80 }, +	{ 0x1F89, 0x1F81 }, +	{ 0x1F8A, 0x1F82 }, +	{ 0x1F8B, 0x1F83 }, +	{ 0x1F8C, 0x1F84 }, +	{ 0x1F8D, 0x1F85 }, +	{ 0x1F8E, 0x1F86 }, +	{ 0x1F8F, 0x1F87 }, +	{ 0x1F98, 0x1F90 }, +	{ 0x1F99, 0x1F91 }, +	{ 0x1F9A, 0x1F92 }, +	{ 0x1F9B, 0x1F93 }, +	{ 0x1F9C, 0x1F94 }, +	{ 0x1F9D, 0x1F95 }, +	{ 0x1F9E, 0x1F96 }, +	{ 0x1F9F, 0x1F97 }, +	{ 0x1FA8, 0x1FA0 }, +	{ 0x1FA9, 0x1FA1 }, +	{ 0x1FAA, 0x1FA2 }, +	{ 0x1FAB, 0x1FA3 }, +	{ 0x1FAC, 0x1FA4 }, +	{ 0x1FAD, 0x1FA5 }, +	{ 0x1FAE, 0x1FA6 }, +	{ 0x1FAF, 0x1FA7 }, +	{ 0x1FB8, 0x1FB0 }, +	{ 0x1FB9, 0x1FB1 }, +	{ 0x1FD8, 0x1FD0 }, +	{ 0x1FD9, 0x1FD1 }, +	{ 0x1FE8, 0x1FE0 }, +	{ 0x1FE9, 0x1FE1 }, +	{ 0x24B6, 0x24D0 }, +	{ 0x24B7, 0x24D1 }, +	{ 0x24B8, 0x24D2 }, +	{ 0x24B9, 0x24D3 }, +	{ 0x24BA, 0x24D4 }, +	{ 0x24BB, 0x24D5 }, +	{ 0x24BC, 0x24D6 }, +	{ 0x24BD, 0x24D7 }, +	{ 0x24BE, 0x24D8 }, +	{ 0x24BF, 0x24D9 }, +	{ 0x24C0, 0x24DA }, +	{ 0x24C1, 0x24DB }, +	{ 0x24C2, 0x24DC }, +	{ 0x24C3, 0x24DD }, +	{ 0x24C4, 0x24DE }, +	{ 0x24C5, 0x24DF }, +	{ 0x24C6, 0x24E0 }, +	{ 0x24C7, 0x24E1 }, +	{ 0x24C8, 0x24E2 }, +	{ 0x24C9, 0x24E3 }, +	{ 0x24CA, 0x24E4 }, +	{ 0x24CB, 0x24E5 }, +	{ 0x24CC, 0x24E6 }, +	{ 0x24CD, 0x24E7 }, +	{ 0x24CE, 0x24E8 }, +	{ 0x24CF, 0x24E9 }, +	{ 0xFF21, 0xFF41 }, +	{ 0xFF22, 0xFF42 }, +	{ 0xFF23, 0xFF43 }, +	{ 0xFF24, 0xFF44 }, +	{ 0xFF25, 0xFF45 }, +	{ 0xFF26, 0xFF46 }, +	{ 0xFF27, 0xFF47 }, +	{ 0xFF28, 0xFF48 }, +	{ 0xFF29, 0xFF49 }, +	{ 0xFF2A, 0xFF4A }, +	{ 0xFF2B, 0xFF4B }, +	{ 0xFF2C, 0xFF4C }, +	{ 0xFF2D, 0xFF4D }, +	{ 0xFF2E, 0xFF4E }, +	{ 0xFF2F, 0xFF4F }, +	{ 0xFF30, 0xFF50 }, +	{ 0xFF31, 0xFF51 }, +	{ 0xFF32, 0xFF52 }, +	{ 0xFF33, 0xFF53 }, +	{ 0xFF34, 0xFF54 }, +	{ 0xFF35, 0xFF55 }, +	{ 0xFF36, 0xFF56 }, +	{ 0xFF37, 0xFF57 }, +	{ 0xFF38, 0xFF58 }, +	{ 0xFF39, 0xFF59 }, +	{ 0xFF3A, 0xFF5A }, +}; + +static int _find_upper(int ch) { +	int low = 0; +	int high = CAPS_LEN - 1; +	int middle; + +	while (low <= high) { +		middle = (low + high) / 2; + +		if (ch < caps_table[middle][0]) { +			high = middle - 1; //search low end of array +		} else if (caps_table[middle][0] < ch) { +			low = middle + 1; //search high end of array +		} else { +			return caps_table[middle][1]; +		} +	} + +	return ch; +} + +static int _find_lower(int ch) { +	int low = 0; +	int high = CAPS_LEN - 2; +	int middle; + +	while (low <= high) { +		middle = (low + high) / 2; + +		if (ch < reverse_caps_table[middle][0]) { +			high = middle - 1; //search low end of array +		} else if (reverse_caps_table[middle][0] < ch) { +			low = middle + 1; //search high end of array +		} else { +			return reverse_caps_table[middle][1]; +		} +	} + +	return ch; +} + +#endif // UCAPS_H diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp new file mode 100644 index 0000000000..213578485e --- /dev/null +++ b/core/string/ustring.cpp @@ -0,0 +1,4898 @@ +/*************************************************************************/ +/*  ustring.cpp                                                          */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "ustring.h" + +#include "core/crypto/crypto_core.h" +#include "core/math/color.h" +#include "core/math/math_funcs.h" +#include "core/os/memory.h" +#include "core/string/print_string.h" +#include "core/string/translation.h" +#include "core/string/ucaps.h" +#include "core/variant/variant.h" + +#include <cstdint> + +#ifndef NO_USE_STDLIB +#include <stdio.h> +#include <stdlib.h> +#endif + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy +#endif + +#if defined(MINGW_ENABLED) || defined(_MSC_VER) +#define snprintf _snprintf_s +#endif + +#define MAX_DIGITS 6 +#define UPPERCASE(m_c) (((m_c) >= 'a' && (m_c) <= 'z') ? ((m_c) - ('a' - 'A')) : (m_c)) +#define LOWERCASE(m_c) (((m_c) >= 'A' && (m_c) <= 'Z') ? ((m_c) + ('a' - 'A')) : (m_c)) +#define IS_DIGIT(m_d) ((m_d) >= '0' && (m_d) <= '9') +#define IS_HEX_DIGIT(m_d) (((m_d) >= '0' && (m_d) <= '9') || ((m_d) >= 'a' && (m_d) <= 'f') || ((m_d) >= 'A' && (m_d) <= 'F')) + +const char CharString::_null = 0; +const char16_t Char16String::_null = 0; +const char32_t String::_null = 0; + +bool is_symbol(char32_t c) { +	return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' '); +} + +bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) { +	const String &s = p_s; +	int beg = CLAMP(p_col, 0, s.length()); +	int end = beg; + +	if (s[beg] > 32 || beg == s.length()) { +		bool symbol = beg < s.length() && is_symbol(s[beg]); + +		while (beg > 0 && s[beg - 1] > 32 && (symbol == is_symbol(s[beg - 1]))) { +			beg--; +		} +		while (end < s.length() && s[end + 1] > 32 && (symbol == is_symbol(s[end + 1]))) { +			end++; +		} + +		if (end < s.length()) { +			end += 1; +		} + +		r_beg = beg; +		r_end = end; + +		return true; +	} else { +		return false; +	} +} + +/*************************************************************************/ +/*  Char16String                                                         */ +/*************************************************************************/ + +bool Char16String::operator<(const Char16String &p_right) const { +	if (length() == 0) { +		return p_right.length() != 0; +	} + +	return is_str_less(get_data(), p_right.get_data()); +} + +Char16String &Char16String::operator+=(char16_t p_char) { +	resize(size() ? size() + 1 : 2); +	set(length(), 0); +	set(length() - 1, p_char); + +	return *this; +} + +Char16String &Char16String::operator=(const char16_t *p_cstr) { +	copy_from(p_cstr); +	return *this; +} + +const char16_t *Char16String::get_data() const { +	if (size()) { +		return &operator[](0); +	} else { +		return u""; +	} +} + +void Char16String::copy_from(const char16_t *p_cstr) { +	if (!p_cstr) { +		resize(0); +		return; +	} + +	const char16_t *s = p_cstr; +	for (; *s; s++) { +	} +	size_t len = s - p_cstr; + +	if (len == 0) { +		resize(0); +		return; +	} + +	Error err = resize(++len); // include terminating null char + +	ERR_FAIL_COND_MSG(err != OK, "Failed to copy char16_t string."); + +	memcpy(ptrw(), p_cstr, len * sizeof(char16_t)); +} + +/*************************************************************************/ +/*  CharString                                                           */ +/*************************************************************************/ + +bool CharString::operator<(const CharString &p_right) const { +	if (length() == 0) { +		return p_right.length() != 0; +	} + +	return is_str_less(get_data(), p_right.get_data()); +} + +CharString &CharString::operator+=(char p_char) { +	resize(size() ? size() + 1 : 2); +	set(length(), 0); +	set(length() - 1, p_char); + +	return *this; +} + +CharString &CharString::operator=(const char *p_cstr) { +	copy_from(p_cstr); +	return *this; +} + +const char *CharString::get_data() const { +	if (size()) { +		return &operator[](0); +	} else { +		return ""; +	} +} + +void CharString::copy_from(const char *p_cstr) { +	if (!p_cstr) { +		resize(0); +		return; +	} + +	size_t len = strlen(p_cstr); + +	if (len == 0) { +		resize(0); +		return; +	} + +	Error err = resize(++len); // include terminating null char + +	ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string."); + +	memcpy(ptrw(), p_cstr, len); +} + +/*************************************************************************/ +/*  String                                                               */ +/*************************************************************************/ + +//TODO: move to TextServer +//kind of poor should be rewritten properly +String String::word_wrap(int p_chars_per_line) const { +	int from = 0; +	int last_space = 0; +	String ret; +	for (int i = 0; i < length(); i++) { +		if (i - from >= p_chars_per_line) { +			if (last_space == -1) { +				ret += substr(from, i - from + 1) + "\n"; +			} else { +				ret += substr(from, last_space - from) + "\n"; +				i = last_space; //rewind +			} +			from = i + 1; +			last_space = -1; +		} else if (operator[](i) == ' ' || operator[](i) == '\t') { +			last_space = i; +		} else if (operator[](i) == '\n') { +			ret += substr(from, i - from) + "\n"; +			from = i + 1; +			last_space = -1; +		} +	} + +	if (from < length()) { +		ret += substr(from, length()); +	} + +	return ret; +} + +void String::copy_from(const char *p_cstr) { +	// copy Latin-1 encoded c-string directly +	if (!p_cstr) { +		resize(0); +		return; +	} + +	int len = 0; +	const char *ptr = p_cstr; +	while (*(ptr++) != 0) { +		len++; +	} + +	if (len == 0) { +		resize(0); +		return; +	} + +	resize(len + 1); // include 0 + +	char32_t *dst = this->ptrw(); + +	for (int i = 0; i < len + 1; i++) { +		dst[i] = p_cstr[i]; +	} +} + +void String::copy_from(const char *p_cstr, const int p_clip_to) { +	// copy Latin-1 encoded c-string directly +	if (!p_cstr) { +		resize(0); +		return; +	} + +	int len = 0; +	const char *ptr = p_cstr; +	while ((p_clip_to < 0 || len < p_clip_to) && *(ptr++) != 0) { +		len++; +	} + +	if (len == 0) { +		resize(0); +		return; +	} + +	resize(len + 1); // include 0 + +	char32_t *dst = this->ptrw(); + +	for (int i = 0; i < len; i++) { +		dst[i] = p_cstr[i]; +	} +	dst[len] = 0; +} + +void String::copy_from(const wchar_t *p_cstr) { +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit, parse as UTF-16 +	parse_utf16((const char16_t *)p_cstr); +#else +	// wchar_t is 32-bit, copy directly +	copy_from((const char32_t *)p_cstr); +#endif +} + +void String::copy_from(const wchar_t *p_cstr, const int p_clip_to) { +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit, parse as UTF-16 +	parse_utf16((const char16_t *)p_cstr, p_clip_to); +#else +	// wchar_t is 32-bit, copy directly +	copy_from((const char32_t *)p_cstr, p_clip_to); +#endif +} + +void String::copy_from(const char32_t &p_char) { +	resize(2); +	if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) { +		print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + "."); +		set(0, 0xfffd); +	} else { +		set(0, p_char); +	} +	set(1, 0); +} + +void String::copy_from(const char32_t *p_cstr) { +	if (!p_cstr) { +		resize(0); +		return; +	} + +	int len = 0; +	const char32_t *ptr = p_cstr; +	while (*(ptr++) != 0) { +		len++; +	} + +	if (len == 0) { +		resize(0); +		return; +	} + +	copy_from_unchecked(p_cstr, len); +} + +void String::copy_from(const char32_t *p_cstr, const int p_clip_to) { +	if (!p_cstr) { +		resize(0); +		return; +	} + +	int len = 0; +	const char32_t *ptr = p_cstr; +	while ((p_clip_to < 0 || len < p_clip_to) && *(ptr++) != 0) { +		len++; +	} + +	if (len == 0) { +		resize(0); +		return; +	} + +	copy_from_unchecked(p_cstr, len); +} + +// assumes the following have already been validated: +// p_char != nullptr +// p_length > 0 +// p_length <= p_char strlen +void String::copy_from_unchecked(const char32_t *p_char, const int p_length) { +	resize(p_length + 1); +	set(p_length, 0); + +	char32_t *dst = ptrw(); + +	for (int i = 0; i < p_length; i++) { +		if ((p_char[i] >= 0xd800 && p_char[i] <= 0xdfff) || (p_char[i] > 0x10ffff)) { +			print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char[i], 16) + "."); +			dst[i] = 0xfffd; +		} else { +			dst[i] = p_char[i]; +		} +	} +} + +void String::operator=(const char *p_str) { +	copy_from(p_str); +} + +void String::operator=(const char32_t *p_str) { +	copy_from(p_str); +} + +void String::operator=(const wchar_t *p_str) { +	copy_from(p_str); +} + +String String::operator+(const String &p_str) const { +	String res = *this; +	res += p_str; +	return res; +} + +String operator+(const char *p_chr, const String &p_str) { +	String tmp = p_chr; +	tmp += p_str; +	return tmp; +} + +String operator+(const wchar_t *p_chr, const String &p_str) { +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit +	String tmp = String::utf16((const char16_t *)p_chr); +#else +	// wchar_t is 32-bi +	String tmp = (const char32_t *)p_chr; +#endif +	tmp += p_str; +	return tmp; +} + +String operator+(char32_t p_chr, const String &p_str) { +	return (String::chr(p_chr) + p_str); +} + +String &String::operator+=(const String &p_str) { +	if (empty()) { +		*this = p_str; +		return *this; +	} + +	if (p_str.empty()) { +		return *this; +	} + +	int from = length(); + +	resize(length() + p_str.size()); + +	const char32_t *src = p_str.get_data(); +	char32_t *dst = ptrw(); + +	set(length(), 0); + +	for (int i = 0; i < p_str.length(); i++) { +		dst[from + i] = src[i]; +	} + +	return *this; +} + +String &String::operator+=(const char *p_str) { +	if (!p_str || p_str[0] == 0) { +		return *this; +	} + +	int src_len = 0; +	const char *ptr = p_str; +	while (*(ptr++) != 0) { +		src_len++; +	} + +	int from = length(); + +	resize(from + src_len + 1); + +	char32_t *dst = ptrw(); + +	set(length(), 0); + +	for (int i = 0; i < src_len; i++) { +		dst[from + i] = p_str[i]; +	} + +	return *this; +} + +String &String::operator+=(const wchar_t *p_str) { +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit +	*this += String::utf16((const char16_t *)p_str); +#else +	// wchar_t is 32-bit +	*this += String((const char32_t *)p_str); +#endif +	return *this; +} + +String &String::operator+=(const char32_t *p_str) { +	*this += String(p_str); +	return *this; +} + +String &String::operator+=(char32_t p_char) { +	resize(size() ? size() + 1 : 2); +	set(length(), 0); +	if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) { +		print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + "."); +		set(length() - 1, 0xfffd); +	} else { +		set(length() - 1, p_char); +	} + +	return *this; +} + +bool String::operator==(const char *p_str) const { +	// compare Latin-1 encoded c-string +	int len = 0; +	const char *aux = p_str; + +	while (*(aux++) != 0) { +		len++; +	} + +	if (length() != len) { +		return false; +	} +	if (empty()) { +		return true; +	} + +	int l = length(); + +	const char32_t *dst = get_data(); + +	// Compare char by char +	for (int i = 0; i < l; i++) { +		if ((char32_t)p_str[i] != dst[i]) { +			return false; +		} +	} + +	return true; +} + +bool String::operator==(const wchar_t *p_str) const { +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit, parse as UTF-16 +	return *this == String::utf16((const char16_t *)p_str); +#else +	// wchar_t is 32-bit, compare char by char +	return *this == (const char32_t *)p_str; +#endif +} + +bool String::operator==(const char32_t *p_str) const { +	int len = 0; +	const char32_t *aux = p_str; + +	while (*(aux++) != 0) { +		len++; +	} + +	if (length() != len) { +		return false; +	} +	if (empty()) { +		return true; +	} + +	int l = length(); + +	const char32_t *dst = get_data(); + +	/* Compare char by char */ +	for (int i = 0; i < l; i++) { +		if (p_str[i] != dst[i]) { +			return false; +		} +	} + +	return true; +} + +bool String::operator==(const String &p_str) const { +	if (length() != p_str.length()) { +		return false; +	} +	if (empty()) { +		return true; +	} + +	int l = length(); + +	const char32_t *src = get_data(); +	const char32_t *dst = p_str.get_data(); + +	/* Compare char by char */ +	for (int i = 0; i < l; i++) { +		if (src[i] != dst[i]) { +			return false; +		} +	} + +	return true; +} + +bool String::operator==(const StrRange &p_str_range) const { +	int len = p_str_range.len; + +	if (length() != len) { +		return false; +	} +	if (empty()) { +		return true; +	} + +	const char32_t *c_str = p_str_range.c_str; +	const char32_t *dst = &operator[](0); + +	/* Compare char by char */ +	for (int i = 0; i < len; i++) { +		if (c_str[i] != dst[i]) { +			return false; +		} +	} + +	return true; +} + +bool operator==(const char *p_chr, const String &p_str) { +	return p_str == p_chr; +} + +bool operator==(const wchar_t *p_chr, const String &p_str) { +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit +	return p_str == String::utf16((const char16_t *)p_chr); +#else +	// wchar_t is 32-bi +	return p_str == String((const char32_t *)p_chr); +#endif +} + +bool operator!=(const char *p_chr, const String &p_str) { +	return !(p_str == p_chr); +} + +bool operator!=(const wchar_t *p_chr, const String &p_str) { +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit +	return !(p_str == String::utf16((const char16_t *)p_chr)); +#else +	// wchar_t is 32-bi +	return !(p_str == String((const char32_t *)p_chr)); +#endif +} + +bool String::operator!=(const char *p_str) const { +	return (!(*this == p_str)); +} + +bool String::operator!=(const wchar_t *p_str) const { +	return (!(*this == p_str)); +} + +bool String::operator!=(const char32_t *p_str) const { +	return (!(*this == p_str)); +} + +bool String::operator!=(const String &p_str) const { +	return !((*this == p_str)); +} + +bool String::operator<=(const String &p_str) const { +	return !(p_str < *this); +} + +bool String::operator>(const String &p_str) const { +	return p_str < *this; +} +bool String::operator>=(const String &p_str) const { +	return !(*this < p_str); +} + +bool String::operator<(const char *p_str) const { +	if (empty() && p_str[0] == 0) { +		return false; +	} +	if (empty()) { +		return true; +	} +	return is_str_less(get_data(), p_str); +} + +bool String::operator<(const wchar_t *p_str) const { +	if (empty() && p_str[0] == 0) { +		return false; +	} +	if (empty()) { +		return true; +	} + +#ifdef WINDOWS_ENABLED +	// wchar_t is 16-bit +	return is_str_less(get_data(), String::utf16((const char16_t *)p_str).get_data()); +#else +	// wchar_t is 32-bit +	return is_str_less(get_data(), (const char32_t *)p_str); +#endif +} + +bool String::operator<(const char32_t *p_str) const { +	if (empty() && p_str[0] == 0) { +		return false; +	} +	if (empty()) { +		return true; +	} + +	return is_str_less(get_data(), p_str); +} + +bool String::operator<(const String &p_str) const { +	return operator<(p_str.get_data()); +} + +signed char String::nocasecmp_to(const String &p_str) const { +	if (empty() && p_str.empty()) { +		return 0; +	} +	if (empty()) { +		return -1; +	} +	if (p_str.empty()) { +		return 1; +	} + +	const char32_t *that_str = p_str.get_data(); +	const char32_t *this_str = get_data(); + +	while (true) { +		if (*that_str == 0 && *this_str == 0) { +			return 0; //we're equal +		} else if (*this_str == 0) { +			return -1; //if this is empty, and the other one is not, then we're less.. I think? +		} else if (*that_str == 0) { +			return 1; //otherwise the other one is smaller.. +		} else if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than +			return -1; +		} else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than +			return 1; +		} + +		this_str++; +		that_str++; +	} +} + +signed char String::casecmp_to(const String &p_str) const { +	if (empty() && p_str.empty()) { +		return 0; +	} +	if (empty()) { +		return -1; +	} +	if (p_str.empty()) { +		return 1; +	} + +	const char32_t *that_str = p_str.get_data(); +	const char32_t *this_str = get_data(); + +	while (true) { +		if (*that_str == 0 && *this_str == 0) { +			return 0; //we're equal +		} else if (*this_str == 0) { +			return -1; //if this is empty, and the other one is not, then we're less.. I think? +		} else if (*that_str == 0) { +			return 1; //otherwise the other one is smaller.. +		} else if (*this_str < *that_str) { //more than +			return -1; +		} else if (*this_str > *that_str) { //less than +			return 1; +		} + +		this_str++; +		that_str++; +	} +} + +signed char String::naturalnocasecmp_to(const String &p_str) const { +	const char32_t *this_str = get_data(); +	const char32_t *that_str = p_str.get_data(); + +	if (this_str && that_str) { +		while (*this_str == '.' || *that_str == '.') { +			if (*this_str++ != '.') { +				return 1; +			} +			if (*that_str++ != '.') { +				return -1; +			} +			if (!*that_str) { +				return 1; +			} +			if (!*this_str) { +				return -1; +			} +		} + +		while (*this_str) { +			if (!*that_str) { +				return 1; +			} else if (IS_DIGIT(*this_str)) { +				if (!IS_DIGIT(*that_str)) { +					return -1; +				} + +				// Keep ptrs to start of numerical sequences +				const char32_t *this_substr = this_str; +				const char32_t *that_substr = that_str; + +				// Compare lengths of both numerical sequences, ignoring leading zeros +				while (IS_DIGIT(*this_str)) { +					this_str++; +				} +				while (IS_DIGIT(*that_str)) { +					that_str++; +				} +				while (*this_substr == '0') { +					this_substr++; +				} +				while (*that_substr == '0') { +					that_substr++; +				} +				int this_len = this_str - this_substr; +				int that_len = that_str - that_substr; + +				if (this_len < that_len) { +					return -1; +				} else if (this_len > that_len) { +					return 1; +				} + +				// If lengths equal, compare lexicographically +				while (this_substr != this_str && that_substr != that_str) { +					if (*this_substr < *that_substr) { +						return -1; +					} else if (*this_substr > *that_substr) { +						return 1; +					} +					this_substr++; +					that_substr++; +				} +			} else if (IS_DIGIT(*that_str)) { +				return 1; +			} else { +				if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than +					return -1; +				} else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than +					return 1; +				} + +				this_str++; +				that_str++; +			} +		} +		if (*that_str) { +			return -1; +		} +	} + +	return 0; +} + +const char32_t *String::get_data() const { +	static const char32_t zero = 0; +	return size() ? &operator[](0) : &zero; +} + +void String::erase(int p_pos, int p_chars) { +	*this = left(p_pos) + substr(p_pos + p_chars, length() - ((p_pos + p_chars))); +} + +String String::capitalize() const { +	String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges(); +	String cap; +	for (int i = 0; i < aux.get_slice_count(" "); i++) { +		String slice = aux.get_slicec(' ', i); +		if (slice.length() > 0) { +			slice[0] = _find_upper(slice[0]); +			if (i > 0) { +				cap += " "; +			} +			cap += slice; +		} +	} + +	return cap; +} + +String String::camelcase_to_underscore(bool lowercase) const { +	const char32_t *cstr = get_data(); +	String new_string; +	const char A = 'A', Z = 'Z'; +	const char a = 'a', z = 'z'; +	int start_index = 0; + +	for (int i = 1; i < this->size(); i++) { +		bool is_upper = cstr[i] >= A && cstr[i] <= Z; +		bool is_number = cstr[i] >= '0' && cstr[i] <= '9'; +		bool are_next_2_lower = false; +		bool is_next_lower = false; +		bool is_next_number = false; +		bool was_precedent_upper = cstr[i - 1] >= A && cstr[i - 1] <= Z; +		bool was_precedent_number = cstr[i - 1] >= '0' && cstr[i - 1] <= '9'; + +		if (i + 2 < this->size()) { +			are_next_2_lower = cstr[i + 1] >= a && cstr[i + 1] <= z && cstr[i + 2] >= a && cstr[i + 2] <= z; +		} + +		if (i + 1 < this->size()) { +			is_next_lower = cstr[i + 1] >= a && cstr[i + 1] <= z; +			is_next_number = cstr[i + 1] >= '0' && cstr[i + 1] <= '9'; +		} + +		const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number; +		const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower; +		const bool cond_c = is_number && !was_precedent_number; +		const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower; +		const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number); + +		bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number; +		if (should_split) { +			new_string += this->substr(start_index, i - start_index) + "_"; +			start_index = i; +		} +	} + +	new_string += this->substr(start_index, this->size() - start_index); +	return lowercase ? new_string.to_lower() : new_string; +} + +String String::get_with_code_lines() const { +	const Vector<String> lines = split("\n"); +	String ret; +	for (int i = 0; i < lines.size(); i++) { +		if (i > 0) { +			ret += "\n"; +		} +		ret += vformat("%4d | %s", i + 1, lines[i]); +	} +	return ret; +} + +int String::get_slice_count(String p_splitter) const { +	if (empty()) { +		return 0; +	} +	if (p_splitter.empty()) { +		return 0; +	} + +	int pos = 0; +	int slices = 1; + +	while ((pos = find(p_splitter, pos)) >= 0) { +		slices++; +		pos += p_splitter.length(); +	} + +	return slices; +} + +String String::get_slice(String p_splitter, int p_slice) const { +	if (empty() || p_splitter.empty()) { +		return ""; +	} + +	int pos = 0; +	int prev_pos = 0; +	//int slices=1; +	if (p_slice < 0) { +		return ""; +	} +	if (find(p_splitter) == -1) { +		return *this; +	} + +	int i = 0; +	while (true) { +		pos = find(p_splitter, pos); +		if (pos == -1) { +			pos = length(); //reached end +		} + +		int from = prev_pos; +		//int to=pos; + +		if (p_slice == i) { +			return substr(from, pos - from); +		} + +		if (pos == length()) { //reached end and no find +			break; +		} +		pos += p_splitter.length(); +		prev_pos = pos; +		i++; +	} + +	return ""; //no find! +} + +String String::get_slicec(char32_t p_splitter, int p_slice) const { +	if (empty()) { +		return String(); +	} + +	if (p_slice < 0) { +		return String(); +	} + +	const char32_t *c = this->ptr(); +	int i = 0; +	int prev = 0; +	int count = 0; +	while (true) { +		if (c[i] == 0 || c[i] == p_splitter) { +			if (p_slice == count) { +				return substr(prev, i - prev); +			} else if (c[i] == 0) { +				return String(); +			} else { +				count++; +				prev = i + 1; +			} +		} + +		i++; +	} +} + +Vector<String> String::split_spaces() const { +	Vector<String> ret; +	int from = 0; +	int i = 0; +	int len = length(); +	if (len == 0) { +		return ret; +	} + +	bool inside = false; + +	while (true) { +		bool empty = operator[](i) < 33; + +		if (i == 0) { +			inside = !empty; +		} + +		if (!empty && !inside) { +			inside = true; +			from = i; +		} + +		if (empty && inside) { +			ret.push_back(substr(from, i - from)); +			inside = false; +		} + +		if (i == len) { +			break; +		} +		i++; +	} + +	return ret; +} + +Vector<String> String::split(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const { +	Vector<String> ret; +	int from = 0; +	int len = length(); + +	while (true) { +		int end = find(p_splitter, from); +		if (end < 0) { +			end = len; +		} +		if (p_allow_empty || (end > from)) { +			if (p_maxsplit <= 0) { +				ret.push_back(substr(from, end - from)); +			} else { +				// Put rest of the string and leave cycle. +				if (p_maxsplit == ret.size()) { +					ret.push_back(substr(from, len)); +					break; +				} + +				// Otherwise, push items until positive limit is reached. +				ret.push_back(substr(from, end - from)); +			} +		} + +		if (end == len) { +			break; +		} + +		from = end + p_splitter.length(); +	} + +	return ret; +} + +Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const { +	Vector<String> ret; +	const int len = length(); +	int remaining_len = len; + +	while (true) { +		if (remaining_len < p_splitter.length() || (p_maxsplit > 0 && p_maxsplit == ret.size())) { +			// no room for another splitter or hit max splits, push what's left and we're done +			if (p_allow_empty || remaining_len > 0) { +				ret.push_back(substr(0, remaining_len)); +			} +			break; +		} + +		int left_edge = rfind(p_splitter, remaining_len - p_splitter.length()); + +		if (left_edge < 0) { +			// no more splitters, we're done +			ret.push_back(substr(0, remaining_len)); +			break; +		} + +		int substr_start = left_edge + p_splitter.length(); +		if (p_allow_empty || substr_start < remaining_len) { +			ret.push_back(substr(substr_start, remaining_len - substr_start)); +		} + +		remaining_len = left_edge; +	} + +	ret.invert(); +	return ret; +} + +Vector<float> String::split_floats(const String &p_splitter, bool p_allow_empty) const { +	Vector<float> ret; +	int from = 0; +	int len = length(); + +	while (true) { +		int end = find(p_splitter, from); +		if (end < 0) { +			end = len; +		} +		if (p_allow_empty || (end > from)) { +			ret.push_back(String::to_float(&get_data()[from])); +		} + +		if (end == len) { +			break; +		} + +		from = end + p_splitter.length(); +	} + +	return ret; +} + +Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_allow_empty) const { +	Vector<float> ret; +	int from = 0; +	int len = length(); + +	while (true) { +		int idx; +		int end = findmk(p_splitters, from, &idx); +		int spl_len = 1; +		if (end < 0) { +			end = len; +		} else { +			spl_len = p_splitters[idx].length(); +		} + +		if (p_allow_empty || (end > from)) { +			ret.push_back(String::to_float(&get_data()[from])); +		} + +		if (end == len) { +			break; +		} + +		from = end + spl_len; +	} + +	return ret; +} + +Vector<int> String::split_ints(const String &p_splitter, bool p_allow_empty) const { +	Vector<int> ret; +	int from = 0; +	int len = length(); + +	while (true) { +		int end = find(p_splitter, from); +		if (end < 0) { +			end = len; +		} +		if (p_allow_empty || (end > from)) { +			ret.push_back(String::to_int(&get_data()[from], end - from)); +		} + +		if (end == len) { +			break; +		} + +		from = end + p_splitter.length(); +	} + +	return ret; +} + +Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allow_empty) const { +	Vector<int> ret; +	int from = 0; +	int len = length(); + +	while (true) { +		int idx; +		int end = findmk(p_splitters, from, &idx); +		int spl_len = 1; +		if (end < 0) { +			end = len; +		} else { +			spl_len = p_splitters[idx].length(); +		} + +		if (p_allow_empty || (end > from)) { +			ret.push_back(String::to_int(&get_data()[from], end - from)); +		} + +		if (end == len) { +			break; +		} + +		from = end + spl_len; +	} + +	return ret; +} + +String String::join(Vector<String> parts) const { +	String ret; +	for (int i = 0; i < parts.size(); ++i) { +		if (i > 0) { +			ret += *this; +		} +		ret += parts[i]; +	} +	return ret; +} + +char32_t String::char_uppercase(char32_t p_char) { +	return _find_upper(p_char); +} + +char32_t String::char_lowercase(char32_t p_char) { +	return _find_lower(p_char); +} + +String String::to_upper() const { +	String upper = *this; + +	for (int i = 0; i < upper.size(); i++) { +		const char32_t s = upper[i]; +		const char32_t t = _find_upper(s); +		if (s != t) { // avoid copy on write +			upper[i] = t; +		} +	} + +	return upper; +} + +String String::to_lower() const { +	String lower = *this; + +	for (int i = 0; i < lower.size(); i++) { +		const char32_t s = lower[i]; +		const char32_t t = _find_lower(s); +		if (s != t) { // avoid copy on write +			lower[i] = t; +		} +	} + +	return lower; +} + +String String::chr(char32_t p_char) { +	char32_t c[2] = { p_char, 0 }; +	return String(c); +} + +String String::num(double p_num, int p_decimals) { +	if (Math::is_nan(p_num)) { +		return "nan"; +	} + +	if (Math::is_inf(p_num)) { +		if (signbit(p_num)) { +			return "-inf"; +		} else { +			return "inf"; +		} +	} +#ifndef NO_USE_STDLIB + +	if (p_decimals > 16) { +		p_decimals = 16; +	} + +	char fmt[7]; +	fmt[0] = '%'; +	fmt[1] = '.'; + +	if (p_decimals < 0) { +		fmt[1] = 'l'; +		fmt[2] = 'f'; +		fmt[3] = 0; + +	} else if (p_decimals < 10) { +		fmt[2] = '0' + p_decimals; +		fmt[3] = 'l'; +		fmt[4] = 'f'; +		fmt[5] = 0; +	} else { +		fmt[2] = '0' + (p_decimals / 10); +		fmt[3] = '0' + (p_decimals % 10); +		fmt[4] = 'l'; +		fmt[5] = 'f'; +		fmt[6] = 0; +	} +	char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) +	snprintf(buf, 256, fmt, p_num); +#else +	sprintf(buf, fmt, p_num); +#endif + +	buf[255] = 0; +	//destroy trailing zeroes +	{ +		bool period = false; +		int z = 0; +		while (buf[z]) { +			if (buf[z] == '.') { +				period = true; +			} +			z++; +		} + +		if (period) { +			z--; +			while (z > 0) { +				if (buf[z] == '0') { +					buf[z] = 0; +				} else if (buf[z] == '.') { +					buf[z] = 0; +					break; +				} else { +					break; +				} + +				z--; +			} +		} +	} + +	return buf; +#else + +	String s; +	String sd; +	/* integer part */ + +	bool neg = p_num < 0; +	p_num = ABS(p_num); +	int intn = (int)p_num; + +	/* decimal part */ + +	if (p_decimals > 0 || (p_decimals == -1 && (int)p_num != p_num)) { +		double dec = p_num - (double)((int)p_num); + +		int digit = 0; +		if (p_decimals > MAX_DIGITS) +			p_decimals = MAX_DIGITS; + +		int dec_int = 0; +		int dec_max = 0; + +		while (true) { +			dec *= 10.0; +			dec_int = dec_int * 10 + (int)dec % 10; +			dec_max = dec_max * 10 + 9; +			digit++; + +			if (p_decimals == -1) { +				if (digit == MAX_DIGITS) //no point in going to infinite +					break; + +				if ((dec - (double)((int)dec)) < 1e-6) +					break; +			} + +			if (digit == p_decimals) +				break; +		} +		dec *= 10; +		int last = (int)dec % 10; + +		if (last > 5) { +			if (dec_int == dec_max) { +				dec_int = 0; +				intn++; +			} else { +				dec_int++; +			} +		} + +		String decimal; +		for (int i = 0; i < digit; i++) { +			char num[2] = { 0, 0 }; +			num[0] = '0' + dec_int % 10; +			decimal = num + decimal; +			dec_int /= 10; +		} +		sd = '.' + decimal; +	} + +	if (intn == 0) + +		s = "0"; +	else { +		while (intn) { +			char32_t num = '0' + (intn % 10); +			intn /= 10; +			s = num + s; +		} +	} + +	s = s + sd; +	if (neg) +		s = "-" + s; +	return s; +#endif +} + +String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { +	bool sign = p_num < 0; + +	int64_t n = p_num; + +	int chars = 0; +	do { +		n /= base; +		chars++; +	} while (n); + +	if (sign) { +		chars++; +	} +	String s; +	s.resize(chars + 1); +	char32_t *c = s.ptrw(); +	c[chars] = 0; +	n = p_num; +	do { +		int mod = ABS(n % base); +		if (mod >= 10) { +			char a = (capitalize_hex ? 'A' : 'a'); +			c[--chars] = a + (mod - 10); +		} else { +			c[--chars] = '0' + mod; +		} + +		n /= base; +	} while (n); + +	if (sign) { +		c[0] = '-'; +	} + +	return s; +} + +String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) { +	uint64_t n = p_num; + +	int chars = 0; +	do { +		n /= base; +		chars++; +	} while (n); + +	String s; +	s.resize(chars + 1); +	char32_t *c = s.ptrw(); +	c[chars] = 0; +	n = p_num; +	do { +		int mod = n % base; +		if (mod >= 10) { +			char a = (capitalize_hex ? 'A' : 'a'); +			c[--chars] = a + (mod - 10); +		} else { +			c[--chars] = '0' + mod; +		} + +		n /= base; +	} while (n); + +	return s; +} + +String String::num_real(double p_num) { +	if (Math::is_nan(p_num)) { +		return "nan"; +	} + +	if (Math::is_inf(p_num)) { +		if (signbit(p_num)) { +			return "-inf"; +		} else { +			return "inf"; +		} +	} + +	String s; +	String sd; +	/* integer part */ + +	bool neg = p_num < 0; +	p_num = ABS(p_num); +	int intn = (int)p_num; + +	/* decimal part */ + +	if ((int)p_num != p_num) { +		double dec = p_num - (double)((int)p_num); + +		int digit = 0; +		int decimals = MAX_DIGITS; + +		int dec_int = 0; +		int dec_max = 0; + +		while (true) { +			dec *= 10.0; +			dec_int = dec_int * 10 + (int)dec % 10; +			dec_max = dec_max * 10 + 9; +			digit++; + +			if ((dec - (double)((int)dec)) < 1e-6) { +				break; +			} + +			if (digit == decimals) { +				break; +			} +		} + +		dec *= 10; +		int last = (int)dec % 10; + +		if (last > 5) { +			if (dec_int == dec_max) { +				dec_int = 0; +				intn++; +			} else { +				dec_int++; +			} +		} + +		String decimal; +		for (int i = 0; i < digit; i++) { +			char num[2] = { 0, 0 }; +			num[0] = '0' + dec_int % 10; +			decimal = num + decimal; +			dec_int /= 10; +		} +		sd = '.' + decimal; +	} else { +		sd = ".0"; +	} + +	if (intn == 0) { +		s = "0"; +	} else { +		while (intn) { +			char32_t num = '0' + (intn % 10); +			intn /= 10; +			s = num + s; +		} +	} + +	s = s + sd; +	if (neg) { +		s = "-" + s; +	} +	return s; +} + +String String::num_scientific(double p_num) { +	if (Math::is_nan(p_num)) { +		return "nan"; +	} + +	if (Math::is_inf(p_num)) { +		if (signbit(p_num)) { +			return "-inf"; +		} else { +			return "inf"; +		} +	} +#ifndef NO_USE_STDLIB + +	char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + +#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) +	// MinGW and old MSC require _set_output_format() to conform to C99 output for printf +	unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT); +#endif +	snprintf(buf, 256, "%lg", p_num); + +#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) +	_set_output_format(old_exponent_format); +#endif + +#else +	sprintf(buf, "%.16lg", p_num); +#endif + +	buf[255] = 0; + +	return buf; +#else + +	return String::num(p_num); +#endif +} + +String String::md5(const uint8_t *p_md5) { +	return String::hex_encode_buffer(p_md5, 16); +} + +String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) { +	static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +	String ret; +	char v[2] = { 0, 0 }; + +	for (int i = 0; i < p_len; i++) { +		v[0] = hex[p_buffer[i] >> 4]; +		ret += v; +		v[0] = hex[p_buffer[i] & 0xF]; +		ret += v; +	} + +	return ret; +} + +CharString String::ascii(bool p_allow_extended) const { +	if (!length()) { +		return CharString(); +	} + +	CharString cs; +	cs.resize(size()); + +	for (int i = 0; i < size(); i++) { +		char32_t c = operator[](i); +		if ((c <= 0x7f) || (c <= 0xff && p_allow_extended)) { +			cs[i] = c; +		} else { +			print_error("Unicode parsing error: Cannot represent " + num_int64(c, 16) + " as ASCII/Latin-1 character."); +			cs[i] = 0x20; +		} +	} + +	return cs; +} + +String String::utf8(const char *p_utf8, int p_len) { +	String ret; +	ret.parse_utf8(p_utf8, p_len); + +	return ret; +} + +bool String::parse_utf8(const char *p_utf8, int p_len) { +#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-8?"); + +	if (!p_utf8) { +		return true; +	} + +	String aux; + +	int cstr_size = 0; +	int str_size = 0; + +	/* HANDLE BOM (Byte Order Mark) */ +	if (p_len < 0 || p_len >= 3) { +		bool has_bom = uint8_t(p_utf8[0]) == 0xef && uint8_t(p_utf8[1]) == 0xbb && uint8_t(p_utf8[2]) == 0xbf; +		if (has_bom) { +			//8-bit encoding, byte order has no meaning in UTF-8, just skip it +			if (p_len >= 0) { +				p_len -= 3; +			} +			p_utf8 += 3; +		} +	} + +	{ +		const char *ptrtmp = p_utf8; +		const char *ptrtmp_limit = &p_utf8[p_len]; +		int skip = 0; +		while (ptrtmp != ptrtmp_limit && *ptrtmp) { +			if (skip == 0) { +				uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp); + +				/* Determine the number of characters in sequence */ +				if ((c & 0x80) == 0) { +					skip = 0; +				} else if ((c & 0xe0) == 0xc0) { +					skip = 1; +				} else if ((c & 0xf0) == 0xe0) { +					skip = 2; +				} else if ((c & 0xf8) == 0xf0) { +					skip = 3; +				} else { +					_UNICERROR("invalid skip at " + num_int64(cstr_size)); +					return true; //invalid utf8 +				} + +				if (skip == 1 && (c & 0x1e) == 0) { +					_UNICERROR("overlong rejected at " + num_int64(cstr_size)); +					return true; //reject overlong +				} + +				str_size++; + +			} else { +				--skip; +			} + +			cstr_size++; +			ptrtmp++; +		} + +		if (skip) { +			_UNICERROR("no space left"); +			return true; //not enough spac +		} +	} + +	if (str_size == 0) { +		clear(); +		return false; +	} + +	resize(str_size + 1); +	char32_t *dst = ptrw(); +	dst[str_size] = 0; + +	while (cstr_size) { +		int len = 0; + +		/* Determine the number of characters in sequence */ +		if ((*p_utf8 & 0x80) == 0) { +			len = 1; +		} else if ((*p_utf8 & 0xe0) == 0xc0) { +			len = 2; +		} else if ((*p_utf8 & 0xf0) == 0xe0) { +			len = 3; +		} else if ((*p_utf8 & 0xf8) == 0xf0) { +			len = 4; +		} else { +			_UNICERROR("invalid len"); +			return true; //invalid UTF8 +		} + +		if (len > cstr_size) { +			_UNICERROR("no space left"); +			return true; //not enough space +		} + +		if (len == 2 && (*p_utf8 & 0x1E) == 0) { +			_UNICERROR("no space left"); +			return true; //reject overlong +		} + +		/* Convert the first character */ + +		uint32_t unichar = 0; + +		if (len == 1) { +			unichar = *p_utf8; +		} else { +			unichar = (0xff >> (len + 1)) & *p_utf8; + +			for (int i = 1; i < len; i++) { +				if ((p_utf8[i] & 0xc0) != 0x80) { +					_UNICERROR("invalid utf8"); +					return true; //invalid utf8 +				} +				if (unichar == 0 && i == 2 && ((p_utf8[i] & 0x7f) >> (7 - len)) == 0) { +					_UNICERROR("invalid utf8 overlong"); +					return true; //no overlong +				} +				unichar = (unichar << 6) | (p_utf8[i] & 0x3f); +			} +		} +		if (unichar >= 0xd800 && unichar <= 0xdfff) { +			_UNICERROR("invalid code point"); +			return CharString(); +		} + +		*(dst++) = unichar; +		cstr_size -= len; +		p_utf8 += len; +	} + +	return false; +#undef _UNICERROR +} + +CharString String::utf8() const { +	int l = length(); +	if (!l) { +		return CharString(); +	} + +	const char32_t *d = &operator[](0); +	int fl = 0; +	for (int i = 0; i < l; i++) { +		uint32_t c = d[i]; +		if (c <= 0x7f) { // 7 bits. +			fl += 1; +		} else if (c <= 0x7ff) { // 11 bits +			fl += 2; +		} else if (c <= 0xffff) { // 16 bits +			fl += 3; +		} else if (c <= 0x0010ffff) { // 21 bits +			fl += 4; +		} else { +			print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + "."); +			return CharString(); +		} +		if (c >= 0xd800 && c <= 0xdfff) { +			print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + "."); +			return CharString(); +		} +	} + +	CharString utf8s; +	if (fl == 0) { +		return utf8s; +	} + +	utf8s.resize(fl + 1); +	uint8_t *cdst = (uint8_t *)utf8s.get_data(); + +#define APPEND_CHAR(m_c) *(cdst++) = m_c + +	for (int i = 0; i < l; i++) { +		uint32_t c = d[i]; + +		if (c <= 0x7f) { // 7 bits. +			APPEND_CHAR(c); +		} else if (c <= 0x7ff) { // 11 bits +			APPEND_CHAR(uint32_t(0xc0 | ((c >> 6) & 0x1f))); // Top 5 bits. +			APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. +		} else if (c <= 0xffff) { // 16 bits +			APPEND_CHAR(uint32_t(0xe0 | ((c >> 12) & 0x0f))); // Top 4 bits. +			APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Middle 6 bits. +			APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. +		} else { // 21 bits +			APPEND_CHAR(uint32_t(0xf0 | ((c >> 18) & 0x07))); // Top 3 bits. +			APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper middle 6 bits. +			APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits. +			APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. +		} +	} +#undef APPEND_CHAR +	*cdst = 0; //trailing zero + +	return utf8s; +} + +String String::utf16(const char16_t *p_utf16, int p_len) { +	String ret; +	ret.parse_utf16(p_utf16, p_len); + +	return ret; +} + +bool String::parse_utf16(const char16_t *p_utf16, int p_len) { +#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-16?"); + +	if (!p_utf16) { +		return true; +	} + +	String aux; + +	int cstr_size = 0; +	int str_size = 0; + +	/* HANDLE BOM (Byte Order Mark) */ +	bool byteswap = false; // assume correct endianness if no BOM found +	if (p_len < 0 || p_len >= 1) { +		bool has_bom = false; +		if (uint16_t(p_utf16[0]) == 0xfeff) { // correct BOM, read as is +			has_bom = true; +			byteswap = false; +		} else if (uint16_t(p_utf16[0]) == 0xfffe) { // backwards BOM, swap bytes +			has_bom = true; +			byteswap = true; +		} +		if (has_bom) { +			if (p_len >= 0) { +				p_len -= 1; +			} +			p_utf16 += 1; +		} +	} + +	{ +		const char16_t *ptrtmp = p_utf16; +		const char16_t *ptrtmp_limit = &p_utf16[p_len]; +		int skip = 0; +		while (ptrtmp != ptrtmp_limit && *ptrtmp) { +			uint32_t c = (byteswap) ? BSWAP16(*ptrtmp) : *ptrtmp; +			if (skip == 0) { +				if ((c & 0xfffffc00) == 0xd800) { +					skip = 1; // lead surrogate +				} else if ((c & 0xfffffc00) == 0xdc00) { +					_UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size)); +					return true; // invalid UTF16 +				} else { +					skip = 0; +				} +				str_size++; +			} else { +				if ((c & 0xfffffc00) == 0xdc00) { // trail surrogate +					--skip; +				} else { +					_UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size)); +					return true; // invalid UTF16 +				} +			} + +			cstr_size++; +			ptrtmp++; +		} + +		if (skip) { +			_UNICERROR("no space left"); +			return true; // not enough space +		} +	} + +	if (str_size == 0) { +		clear(); +		return false; +	} + +	resize(str_size + 1); +	char32_t *dst = ptrw(); +	dst[str_size] = 0; + +	while (cstr_size) { +		int len = 0; +		uint32_t c = (byteswap) ? BSWAP16(*p_utf16) : *p_utf16; + +		if ((c & 0xfffffc00) == 0xd800) { +			len = 2; +		} else { +			len = 1; +		} + +		if (len > cstr_size) { +			_UNICERROR("no space left"); +			return true; //not enough space +		} + +		uint32_t unichar = 0; +		if (len == 1) { +			unichar = c; +		} else { +			uint32_t c2 = (byteswap) ? BSWAP16(p_utf16[1]) : p_utf16[1]; +			unichar = (c << 10UL) + c2 - ((0xd800 << 10UL) + 0xdc00 - 0x10000); +		} + +		*(dst++) = unichar; +		cstr_size -= len; +		p_utf16 += len; +	} + +	return false; +#undef _UNICERROR +} + +Char16String String::utf16() const { +	int l = length(); +	if (!l) { +		return Char16String(); +	} + +	const char32_t *d = &operator[](0); +	int fl = 0; +	for (int i = 0; i < l; i++) { +		uint32_t c = d[i]; +		if (c <= 0xffff) { // 16 bits. +			fl += 1; +		} else if (c <= 0x10ffff) { // 32 bits. +			fl += 2; +		} else { +			print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + "."); +			return Char16String(); +		} +		if (c >= 0xd800 && c <= 0xdfff) { +			print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + "."); +			return Char16String(); +		} +	} + +	Char16String utf16s; +	if (fl == 0) { +		return utf16s; +	} + +	utf16s.resize(fl + 1); +	uint16_t *cdst = (uint16_t *)utf16s.get_data(); + +#define APPEND_CHAR(m_c) *(cdst++) = m_c + +	for (int i = 0; i < l; i++) { +		uint32_t c = d[i]; + +		if (c <= 0xffff) { // 16 bits. +			APPEND_CHAR(c); +		} else { // 32 bits. +			APPEND_CHAR(uint32_t((c >> 10) + 0xd7c0)); // lead surrogate. +			APPEND_CHAR(uint32_t((c & 0x3ff) | 0xdc00)); // trail surrogate. +		} +	} +#undef APPEND_CHAR +	*cdst = 0; //trailing zero + +	return utf16s; +} + +String::String(const char *p_str) { +	copy_from(p_str); +} + +String::String(const wchar_t *p_str) { +	copy_from(p_str); +} + +String::String(const char32_t *p_str) { +	copy_from(p_str); +} + +String::String(const char *p_str, int p_clip_to_len) { +	copy_from(p_str, p_clip_to_len); +} + +String::String(const wchar_t *p_str, int p_clip_to_len) { +	copy_from(p_str, p_clip_to_len); +} + +String::String(const char32_t *p_str, int p_clip_to_len) { +	copy_from(p_str, p_clip_to_len); +} + +String::String(const StrRange &p_range) { +	if (!p_range.c_str) { +		return; +	} +	copy_from(p_range.c_str, p_range.len); +} + +int64_t String::hex_to_int(bool p_with_prefix) const { +	if (p_with_prefix && length() < 3) { +		return 0; +	} + +	const char32_t *s = ptr(); + +	int64_t sign = s[0] == '-' ? -1 : 1; + +	if (sign < 0) { +		s++; +	} + +	if (p_with_prefix) { +		if (s[0] != '0' || s[1] != 'x') { +			return 0; +		} +		s += 2; +	} + +	int64_t hex = 0; + +	while (*s) { +		char32_t c = LOWERCASE(*s); +		int64_t n; +		if (c >= '0' && c <= '9') { +			n = c - '0'; +		} else if (c >= 'a' && c <= 'f') { +			n = (c - 'a') + 10; +		} else { +			return 0; +		} +		// Check for overflow/underflow, with special case to ensure INT64_MIN does not result in error +		bool overflow = ((hex > INT64_MAX / 16) && (sign == 1 || (sign == -1 && hex != (INT64_MAX >> 4) + 1))) || (sign == -1 && hex == (INT64_MAX >> 4) + 1 && c > '0'); +		ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small.")); +		hex *= 16; +		hex += n; +		s++; +	} + +	return hex * sign; +} + +int64_t String::bin_to_int(bool p_with_prefix) const { +	if (p_with_prefix && length() < 3) { +		return 0; +	} + +	const char32_t *s = ptr(); + +	int64_t sign = s[0] == '-' ? -1 : 1; + +	if (sign < 0) { +		s++; +	} + +	if (p_with_prefix) { +		if (s[0] != '0' || s[1] != 'b') { +			return 0; +		} +		s += 2; +	} + +	int64_t binary = 0; + +	while (*s) { +		char32_t c = LOWERCASE(*s); +		int64_t n; +		if (c == '0' || c == '1') { +			n = c - '0'; +		} else { +			return 0; +		} +		// Check for overflow/underflow, with special case to ensure INT64_MIN does not result in error +		bool overflow = ((binary > INT64_MAX / 2) && (sign == 1 || (sign == -1 && binary != (INT64_MAX >> 1) + 1))) || (sign == -1 && binary == (INT64_MAX >> 1) + 1 && c > '0'); +		ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small.")); +		binary *= 2; +		binary += n; +		s++; +	} + +	return binary * sign; +} + +int64_t String::to_int() const { +	if (length() == 0) { +		return 0; +	} + +	int to = (find(".") >= 0) ? find(".") : length(); + +	int64_t integer = 0; +	int64_t sign = 1; + +	for (int i = 0; i < to; i++) { +		char32_t c = operator[](i); +		if (c >= '0' && c <= '9') { +			bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); +			ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small.")); +			integer *= 10; +			integer += c - '0'; + +		} else if (integer == 0 && c == '-') { +			sign = -sign; +		} +	} + +	return integer * sign; +} + +int64_t String::to_int(const char *p_str, int p_len) { +	int to = 0; +	if (p_len >= 0) { +		to = p_len; +	} else { +		while (p_str[to] != 0 && p_str[to] != '.') { +			to++; +		} +	} + +	int64_t integer = 0; +	int64_t sign = 1; + +	for (int i = 0; i < to; i++) { +		char c = p_str[i]; +		if (c >= '0' && c <= '9') { +			bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); +			ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as integer, provided value is " + (sign == 1 ? "too big." : "too small.")); +			integer *= 10; +			integer += c - '0'; + +		} else if (c == '-' && integer == 0) { +			sign = -sign; +		} else if (c != ' ') { +			break; +		} +	} + +	return integer * sign; +} + +int64_t String::to_int(const wchar_t *p_str, int p_len) { +	int to = 0; +	if (p_len >= 0) { +		to = p_len; +	} else { +		while (p_str[to] != 0 && p_str[to] != '.') { +			to++; +		} +	} + +	int64_t integer = 0; +	int64_t sign = 1; + +	for (int i = 0; i < to; i++) { +		wchar_t c = p_str[i]; +		if (c >= '0' && c <= '9') { +			bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); +			ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as integer, provided value is " + (sign == 1 ? "too big." : "too small.")); +			integer *= 10; +			integer += c - '0'; + +		} else if (c == '-' && integer == 0) { +			sign = -sign; +		} else if (c != ' ') { +			break; +		} +	} + +	return integer * sign; +} + +bool String::is_numeric() const { +	if (length() == 0) { +		return false; +	} + +	int s = 0; +	if (operator[](0) == '-') { +		++s; +	} +	bool dot = false; +	for (int i = s; i < length(); i++) { +		char32_t c = operator[](i); +		if (c == '.') { +			if (dot) { +				return false; +			} +			dot = true; +		} else if (c < '0' || c > '9') { +			return false; +		} +	} + +	return true; // TODO: Use the parser below for this instead +} + +template <class C> +static double built_in_strtod(const C *string, /* A decimal ASCII floating-point number, +				 * optionally preceded by white space. Must +				 * have form "-I.FE-X", where I is the integer +				 * part of the mantissa, F is the fractional +				 * part of the mantissa, and X is the +				 * exponent. Either of the signs may be "+", +				 * "-", or omitted. Either I or F may be +				 * omitted, or both. The decimal point isn't +				 * necessary unless F is present. The "E" may +				 * actually be an "e". E and X may both be +				 * omitted (but not just one). */ +		C **endPtr = nullptr) /* If non-nullptr, store terminating Cacter's +				 * address here. */ +{ +	static const int maxExponent = 511; /* Largest possible base 10 exponent.  Any +					 * exponent larger than this will already +					 * produce underflow or overflow, so there's +					 * no need to worry about additional digits. +					 */ +	static const double powersOf10[] = { /* Table giving binary powers of 10.  Entry */ +		10., /* is 10^2^i.  Used to convert decimal */ +		100., /* exponents into floating-point numbers. */ +		1.0e4, +		1.0e8, +		1.0e16, +		1.0e32, +		1.0e64, +		1.0e128, +		1.0e256 +	}; + +	bool sign, expSign = false; +	double fraction, dblExp; +	const double *d; +	const C *p; +	int c; +	int exp = 0; /* Exponent read from "EX" field. */ +	int fracExp = 0; /* Exponent that derives from the fractional +				 * part. Under normal circumstances, it is +				 * the negative of the number of digits in F. +				 * However, if I is very long, the last digits +				 * of I get dropped (otherwise a long I with a +				 * large negative exponent could cause an +				 * unnecessary overflow on I alone). In this +				 * case, fracExp is incremented one for each +				 * dropped digit. */ +	int mantSize; /* Number of digits in mantissa. */ +	int decPt; /* Number of mantissa digits BEFORE decimal +				 * point. */ +	const C *pExp; /* Temporarily holds location of exponent in +				 * string. */ + +	/* +     * Strip off leading blanks and check for a sign. +     */ + +	p = string; +	while (*p == ' ' || *p == '\t' || *p == '\n') { +		p += 1; +	} +	if (*p == '-') { +		sign = true; +		p += 1; +	} else { +		if (*p == '+') { +			p += 1; +		} +		sign = false; +	} + +	/* +     * Count the number of digits in the mantissa (including the decimal +     * point), and also locate the decimal point. +     */ + +	decPt = -1; +	for (mantSize = 0;; mantSize += 1) { +		c = *p; +		if (!IS_DIGIT(c)) { +			if ((c != '.') || (decPt >= 0)) { +				break; +			} +			decPt = mantSize; +		} +		p += 1; +	} + +	/* +     * Now suck up the digits in the mantissa. Use two integers to collect 9 +     * digits each (this is faster than using floating-point). If the mantissa +     * has more than 18 digits, ignore the extras, since they can't affect the +     * value anyway. +     */ + +	pExp = p; +	p -= mantSize; +	if (decPt < 0) { +		decPt = mantSize; +	} else { +		mantSize -= 1; /* One of the digits was the point. */ +	} +	if (mantSize > 18) { +		fracExp = decPt - 18; +		mantSize = 18; +	} else { +		fracExp = decPt - mantSize; +	} +	if (mantSize == 0) { +		fraction = 0.0; +		p = string; +		goto done; +	} else { +		int frac1, frac2; + +		frac1 = 0; +		for (; mantSize > 9; mantSize -= 1) { +			c = *p; +			p += 1; +			if (c == '.') { +				c = *p; +				p += 1; +			} +			frac1 = 10 * frac1 + (c - '0'); +		} +		frac2 = 0; +		for (; mantSize > 0; mantSize -= 1) { +			c = *p; +			p += 1; +			if (c == '.') { +				c = *p; +				p += 1; +			} +			frac2 = 10 * frac2 + (c - '0'); +		} +		fraction = (1.0e9 * frac1) + frac2; +	} + +	/* +     * Skim off the exponent. +     */ + +	p = pExp; +	if ((*p == 'E') || (*p == 'e')) { +		p += 1; +		if (*p == '-') { +			expSign = true; +			p += 1; +		} else { +			if (*p == '+') { +				p += 1; +			} +			expSign = false; +		} +		if (!IS_DIGIT(char32_t(*p))) { +			p = pExp; +			goto done; +		} +		while (IS_DIGIT(char32_t(*p))) { +			exp = exp * 10 + (*p - '0'); +			p += 1; +		} +	} +	if (expSign) { +		exp = fracExp - exp; +	} else { +		exp = fracExp + exp; +	} + +	/* +     * Generate a floating-point number that represents the exponent. Do this +     * by processing the exponent one bit at a time to combine many powers of +     * 2 of 10. Then combine the exponent with the fraction. +     */ + +	if (exp < 0) { +		expSign = true; +		exp = -exp; +	} else { +		expSign = false; +	} + +	if (exp > maxExponent) { +		exp = maxExponent; +		WARN_PRINT("Exponent too high"); +	} +	dblExp = 1.0; +	for (d = powersOf10; exp != 0; exp >>= 1, ++d) { +		if (exp & 01) { +			dblExp *= *d; +		} +	} +	if (expSign) { +		fraction /= dblExp; +	} else { +		fraction *= dblExp; +	} + +done: +	if (endPtr != nullptr) { +		*endPtr = (C *)p; +	} + +	if (sign) { +		return -fraction; +	} +	return fraction; +} + +#define READING_SIGN 0 +#define READING_INT 1 +#define READING_DEC 2 +#define READING_EXP 3 +#define READING_DONE 4 + +double String::to_float(const char *p_str) { +	return built_in_strtod<char>(p_str); +} + +double String::to_float(const char32_t *p_str, const char32_t **r_end) { +	return built_in_strtod<char32_t>(p_str, (char32_t **)r_end); +} + +double String::to_float(const wchar_t *p_str, const wchar_t **r_end) { +	return built_in_strtod<wchar_t>(p_str, (wchar_t **)r_end); +} + +int64_t String::to_int(const char32_t *p_str, int p_len, bool p_clamp) { +	if (p_len == 0 || !p_str[0]) { +		return 0; +	} +	///@todo make more exact so saving and loading does not lose precision + +	int64_t integer = 0; +	int64_t sign = 1; +	int reading = READING_SIGN; + +	const char32_t *str = p_str; +	const char32_t *limit = &p_str[p_len]; + +	while (*str && reading != READING_DONE && str != limit) { +		char32_t c = *(str++); +		switch (reading) { +			case READING_SIGN: { +				if (c >= '0' && c <= '9') { +					reading = READING_INT; +					// let it fallthrough +				} else if (c == '-') { +					sign = -1; +					reading = READING_INT; +					break; +				} else if (c == '+') { +					sign = 1; +					reading = READING_INT; +					break; +				} else { +					break; +				} +				[[fallthrough]]; +			} +			case READING_INT: { +				if (c >= '0' && c <= '9') { +					if (integer > INT64_MAX / 10) { +						String number(""); +						str = p_str; +						while (*str && str != limit) { +							number += *(str++); +						} +						if (p_clamp) { +							if (sign == 1) { +								return INT64_MAX; +							} else { +								return INT64_MIN; +							} +						} else { +							ERR_FAIL_V_MSG(sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + number + " as integer, provided value is " + (sign == 1 ? "too big." : "too small.")); +						} +					} +					integer *= 10; +					integer += c - '0'; +				} else { +					reading = READING_DONE; +				} + +			} break; +		} +	} + +	return sign * integer; +} + +double String::to_float() const { +	if (empty()) { +		return 0; +	} +	return built_in_strtod<char32_t>(get_data()); +} + +uint32_t String::hash(const char *p_cstr) { +	uint32_t hashv = 5381; +	uint32_t c; + +	while ((c = *p_cstr++)) { +		hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ +	} + +	return hashv; +} + +uint32_t String::hash(const char *p_cstr, int p_len) { +	uint32_t hashv = 5381; +	for (int i = 0; i < p_len; i++) { +		hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ +	} + +	return hashv; +} + +uint32_t String::hash(const wchar_t *p_cstr, int p_len) { +	uint32_t hashv = 5381; +	for (int i = 0; i < p_len; i++) { +		hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ +	} + +	return hashv; +} + +uint32_t String::hash(const wchar_t *p_cstr) { +	uint32_t hashv = 5381; +	uint32_t c; + +	while ((c = *p_cstr++)) { +		hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ +	} + +	return hashv; +} + +uint32_t String::hash(const char32_t *p_cstr, int p_len) { +	uint32_t hashv = 5381; +	for (int i = 0; i < p_len; i++) { +		hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ +	} + +	return hashv; +} + +uint32_t String::hash(const char32_t *p_cstr) { +	uint32_t hashv = 5381; +	uint32_t c; + +	while ((c = *p_cstr++)) { +		hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ +	} + +	return hashv; +} + +uint32_t String::hash() const { +	/* simple djb2 hashing */ + +	const char32_t *chr = get_data(); +	uint32_t hashv = 5381; +	uint32_t c; + +	while ((c = *chr++)) { +		hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ +	} + +	return hashv; +} + +uint64_t String::hash64() const { +	/* simple djb2 hashing */ + +	const char32_t *chr = get_data(); +	uint64_t hashv = 5381; +	uint64_t c; + +	while ((c = *chr++)) { +		hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ +	} + +	return hashv; +} + +String String::md5_text() const { +	CharString cs = utf8(); +	unsigned char hash[16]; +	CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); +	return String::hex_encode_buffer(hash, 16); +} + +String String::sha1_text() const { +	CharString cs = utf8(); +	unsigned char hash[20]; +	CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); +	return String::hex_encode_buffer(hash, 20); +} + +String String::sha256_text() const { +	CharString cs = utf8(); +	unsigned char hash[32]; +	CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); +	return String::hex_encode_buffer(hash, 32); +} + +Vector<uint8_t> String::md5_buffer() const { +	CharString cs = utf8(); +	unsigned char hash[16]; +	CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); + +	Vector<uint8_t> ret; +	ret.resize(16); +	for (int i = 0; i < 16; i++) { +		ret.write[i] = hash[i]; +	} +	return ret; +} + +Vector<uint8_t> String::sha1_buffer() const { +	CharString cs = utf8(); +	unsigned char hash[20]; +	CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); + +	Vector<uint8_t> ret; +	ret.resize(20); +	for (int i = 0; i < 20; i++) { +		ret.write[i] = hash[i]; +	} + +	return ret; +} + +Vector<uint8_t> String::sha256_buffer() const { +	CharString cs = utf8(); +	unsigned char hash[32]; +	CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); + +	Vector<uint8_t> ret; +	ret.resize(32); +	for (int i = 0; i < 32; i++) { +		ret.write[i] = hash[i]; +	} +	return ret; +} + +String String::insert(int p_at_pos, const String &p_string) const { +	if (p_at_pos < 0) { +		return *this; +	} + +	if (p_at_pos > length()) { +		p_at_pos = length(); +	} + +	String pre; +	if (p_at_pos > 0) { +		pre = substr(0, p_at_pos); +	} + +	String post; +	if (p_at_pos < length()) { +		post = substr(p_at_pos, length() - p_at_pos); +	} + +	return pre + p_string + post; +} + +String String::substr(int p_from, int p_chars) const { +	if (p_chars == -1) { +		p_chars = length() - p_from; +	} + +	if (empty() || p_from < 0 || p_from >= length() || p_chars <= 0) { +		return ""; +	} + +	if ((p_from + p_chars) > length()) { +		p_chars = length() - p_from; +	} + +	if (p_from == 0 && p_chars >= length()) { +		return String(*this); +	} + +	String s = String(); +	s.copy_from_unchecked(&get_data()[p_from], p_chars); +	return s; +} + +int String::find(const String &p_str, int p_from) const { +	if (p_from < 0) { +		return -1; +	} + +	const int src_len = p_str.length(); + +	const int len = length(); + +	if (src_len == 0 || len == 0) { +		return -1; // won't find anything! +	} + +	const char32_t *src = get_data(); +	const char32_t *str = p_str.get_data(); + +	for (int i = p_from; i <= (len - src_len); i++) { +		bool found = true; +		for (int j = 0; j < src_len; j++) { +			int read_pos = i + j; + +			if (read_pos >= len) { +				ERR_PRINT("read_pos>=len"); +				return -1; +			} + +			if (src[read_pos] != str[j]) { +				found = false; +				break; +			} +		} + +		if (found) { +			return i; +		} +	} + +	return -1; +} + +int String::find(const char *p_str, int p_from) const { +	if (p_from < 0) { +		return -1; +	} + +	const int len = length(); + +	if (len == 0) { +		return -1; // won't find anything! +	} + +	const char32_t *src = get_data(); + +	int src_len = 0; +	while (p_str[src_len] != '\0') { +		src_len++; +	} + +	if (src_len == 1) { +		const char32_t needle = p_str[0]; + +		for (int i = p_from; i < len; i++) { +			if (src[i] == needle) { +				return i; +			} +		} + +	} else { +		for (int i = p_from; i <= (len - src_len); i++) { +			bool found = true; +			for (int j = 0; j < src_len; j++) { +				int read_pos = i + j; + +				if (read_pos >= len) { +					ERR_PRINT("read_pos>=len"); +					return -1; +				} + +				if (src[read_pos] != (char32_t)p_str[j]) { +					found = false; +					break; +				} +			} + +			if (found) { +				return i; +			} +		} +	} + +	return -1; +} + +int String::find_char(const char32_t &p_char, int p_from) const { +	return _cowdata.find(p_char, p_from); +} + +int String::findmk(const Vector<String> &p_keys, int p_from, int *r_key) const { +	if (p_from < 0) { +		return -1; +	} +	if (p_keys.size() == 0) { +		return -1; +	} + +	//int src_len=p_str.length(); +	const String *keys = &p_keys[0]; +	int key_count = p_keys.size(); +	int len = length(); + +	if (len == 0) { +		return -1; // won't find anything! +	} + +	const char32_t *src = get_data(); + +	for (int i = p_from; i < len; i++) { +		bool found = true; +		for (int k = 0; k < key_count; k++) { +			found = true; +			if (r_key) { +				*r_key = k; +			} +			const char32_t *cmp = keys[k].get_data(); +			int l = keys[k].length(); + +			for (int j = 0; j < l; j++) { +				int read_pos = i + j; + +				if (read_pos >= len) { +					found = false; +					break; +				} + +				if (src[read_pos] != cmp[j]) { +					found = false; +					break; +				} +			} +			if (found) { +				break; +			} +		} + +		if (found) { +			return i; +		} +	} + +	return -1; +} + +int String::findn(const String &p_str, int p_from) const { +	if (p_from < 0) { +		return -1; +	} + +	int src_len = p_str.length(); + +	if (src_len == 0 || length() == 0) { +		return -1; // won't find anything! +	} + +	const char32_t *srcd = get_data(); + +	for (int i = p_from; i <= (length() - src_len); i++) { +		bool found = true; +		for (int j = 0; j < src_len; j++) { +			int read_pos = i + j; + +			if (read_pos >= length()) { +				ERR_PRINT("read_pos>=length()"); +				return -1; +			} + +			char32_t src = _find_lower(srcd[read_pos]); +			char32_t dst = _find_lower(p_str[j]); + +			if (src != dst) { +				found = false; +				break; +			} +		} + +		if (found) { +			return i; +		} +	} + +	return -1; +} + +int String::rfind(const String &p_str, int p_from) const { +	// establish a limit +	int limit = length() - p_str.length(); +	if (limit < 0) { +		return -1; +	} + +	// establish a starting point +	if (p_from < 0) { +		p_from = limit; +	} else if (p_from > limit) { +		p_from = limit; +	} + +	int src_len = p_str.length(); +	int len = length(); + +	if (src_len == 0 || len == 0) { +		return -1; // won't find anything! +	} + +	const char32_t *src = get_data(); + +	for (int i = p_from; i >= 0; i--) { +		bool found = true; +		for (int j = 0; j < src_len; j++) { +			int read_pos = i + j; + +			if (read_pos >= len) { +				ERR_PRINT("read_pos>=len"); +				return -1; +			} + +			if (src[read_pos] != p_str[j]) { +				found = false; +				break; +			} +		} + +		if (found) { +			return i; +		} +	} + +	return -1; +} + +int String::rfindn(const String &p_str, int p_from) const { +	// establish a limit +	int limit = length() - p_str.length(); +	if (limit < 0) { +		return -1; +	} + +	// establish a starting point +	if (p_from < 0) { +		p_from = limit; +	} else if (p_from > limit) { +		p_from = limit; +	} + +	int src_len = p_str.length(); +	int len = length(); + +	if (src_len == 0 || len == 0) { +		return -1; // won't find anything! +	} + +	const char32_t *src = get_data(); + +	for (int i = p_from; i >= 0; i--) { +		bool found = true; +		for (int j = 0; j < src_len; j++) { +			int read_pos = i + j; + +			if (read_pos >= len) { +				ERR_PRINT("read_pos>=len"); +				return -1; +			} + +			char32_t srcc = _find_lower(src[read_pos]); +			char32_t dstc = _find_lower(p_str[j]); + +			if (srcc != dstc) { +				found = false; +				break; +			} +		} + +		if (found) { +			return i; +		} +	} + +	return -1; +} + +bool String::ends_with(const String &p_string) const { +	int pos = rfind(p_string); +	if (pos == -1) { +		return false; +	} +	return pos + p_string.length() == length(); +} + +bool String::begins_with(const String &p_string) const { +	if (p_string.length() > length()) { +		return false; +	} + +	int l = p_string.length(); +	if (l == 0) { +		return true; +	} + +	const char32_t *src = &p_string[0]; +	const char32_t *str = &operator[](0); + +	int i = 0; +	for (; i < l; i++) { +		if (src[i] != str[i]) { +			return false; +		} +	} + +	// only if i == l the p_string matches the beginning +	return i == l; +} + +bool String::begins_with(const char *p_string) const { +	int l = length(); +	if (l == 0 || !p_string) { +		return false; +	} + +	const char32_t *str = &operator[](0); +	int i = 0; + +	while (*p_string && i < l) { +		if ((char32_t)*p_string != str[i]) { +			return false; +		} +		i++; +		p_string++; +	} + +	return *p_string == 0; +} + +bool String::is_enclosed_in(const String &p_string) const { +	return begins_with(p_string) && ends_with(p_string); +} + +bool String::is_subsequence_of(const String &p_string) const { +	return _base_is_subsequence_of(p_string, false); +} + +bool String::is_subsequence_ofi(const String &p_string) const { +	return _base_is_subsequence_of(p_string, true); +} + +bool String::is_quoted() const { +	return is_enclosed_in("\"") || is_enclosed_in("'"); +} + +int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const { +	if (p_string.empty()) { +		return 0; +	} +	int len = length(); +	int slen = p_string.length(); +	if (len < slen) { +		return 0; +	} +	String str; +	if (p_from >= 0 && p_to >= 0) { +		if (p_to == 0) { +			p_to = len; +		} else if (p_from >= p_to) { +			return 0; +		} +		if (p_from == 0 && p_to == len) { +			str = String(); +			str.copy_from_unchecked(&get_data()[0], len); +		} else { +			str = substr(p_from, p_to - p_from); +		} +	} else { +		return 0; +	} +	int c = 0; +	int idx = -1; +	do { +		idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string); +		if (idx != -1) { +			str = str.substr(idx + slen, str.length() - slen); +			++c; +		} +	} while (idx != -1); +	return c; +} + +int String::count(const String &p_string, int p_from, int p_to) const { +	return _count(p_string, p_from, p_to, false); +} + +int String::countn(const String &p_string, int p_from, int p_to) const { +	return _count(p_string, p_from, p_to, true); +} + +bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const { +	int len = length(); +	if (len == 0) { +		// Technically an empty string is subsequence of any string +		return true; +	} + +	if (len > p_string.length()) { +		return false; +	} + +	const char32_t *src = &operator[](0); +	const char32_t *tgt = &p_string[0]; + +	for (; *src && *tgt; tgt++) { +		bool match = false; +		if (case_insensitive) { +			char32_t srcc = _find_lower(*src); +			char32_t tgtc = _find_lower(*tgt); +			match = srcc == tgtc; +		} else { +			match = *src == *tgt; +		} +		if (match) { +			src++; +			if (!*src) { +				return true; +			} +		} +	} + +	return false; +} + +Vector<String> String::bigrams() const { +	int n_pairs = length() - 1; +	Vector<String> b; +	if (n_pairs <= 0) { +		return b; +	} +	b.resize(n_pairs); +	for (int i = 0; i < n_pairs; i++) { +		b.write[i] = substr(i, 2); +	} +	return b; +} + +// Similarity according to Sorensen-Dice coefficient +float String::similarity(const String &p_string) const { +	if (operator==(p_string)) { +		// Equal strings are totally similar +		return 1.0f; +	} +	if (length() < 2 || p_string.length() < 2) { +		// No way to calculate similarity without a single bigram +		return 0.0f; +	} + +	Vector<String> src_bigrams = bigrams(); +	Vector<String> tgt_bigrams = p_string.bigrams(); + +	int src_size = src_bigrams.size(); +	int tgt_size = tgt_bigrams.size(); + +	double sum = src_size + tgt_size; +	double inter = 0; +	for (int i = 0; i < src_size; i++) { +		for (int j = 0; j < tgt_size; j++) { +			if (src_bigrams[i] == tgt_bigrams[j]) { +				inter++; +				break; +			} +		} +	} + +	return (2.0f * inter) / sum; +} + +static bool _wildcard_match(const char32_t *p_pattern, const char32_t *p_string, bool p_case_sensitive) { +	switch (*p_pattern) { +		case '\0': +			return !*p_string; +		case '*': +			return _wildcard_match(p_pattern + 1, p_string, p_case_sensitive) || (*p_string && _wildcard_match(p_pattern, p_string + 1, p_case_sensitive)); +		case '?': +			return *p_string && (*p_string != '.') && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); +		default: + +			return (p_case_sensitive ? (*p_string == *p_pattern) : (_find_upper(*p_string) == _find_upper(*p_pattern))) && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); +	} +} + +bool String::match(const String &p_wildcard) const { +	if (!p_wildcard.length() || !length()) { +		return false; +	} + +	return _wildcard_match(p_wildcard.get_data(), get_data(), true); +} + +bool String::matchn(const String &p_wildcard) const { +	if (!p_wildcard.length() || !length()) { +		return false; +	} +	return _wildcard_match(p_wildcard.get_data(), get_data(), false); +} + +String String::format(const Variant &values, String placeholder) const { +	String new_string = String(this->ptr()); + +	if (values.get_type() == Variant::ARRAY) { +		Array values_arr = values; + +		for (int i = 0; i < values_arr.size(); i++) { +			String i_as_str = String::num_int64(i); + +			if (values_arr[i].get_type() == Variant::ARRAY) { //Array in Array structure [["name","RobotGuy"],[0,"godot"],["strength",9000.91]] +				Array value_arr = values_arr[i]; + +				if (value_arr.size() == 2) { +					Variant v_key = value_arr[0]; +					String key = v_key; +					if (key.left(1) == "\"" && key.right(key.length() - 1) == "\"") { +						key = key.substr(1, key.length() - 2); +					} + +					Variant v_val = value_arr[1]; +					String val = v_val; + +					if (val.left(1) == "\"" && val.right(val.length() - 1) == "\"") { +						val = val.substr(1, val.length() - 2); +					} + +					new_string = new_string.replace(placeholder.replace("_", key), val); +				} else { +					ERR_PRINT(String("STRING.format Inner Array size != 2 ").ascii().get_data()); +				} +			} else { //Array structure ["RobotGuy","Logis","rookie"] +				Variant v_val = values_arr[i]; +				String val = v_val; + +				if (val.left(1) == "\"" && val.right(val.length() - 1) == "\"") { +					val = val.substr(1, val.length() - 2); +				} + +				if (placeholder.find("_") > -1) { +					new_string = new_string.replace(placeholder.replace("_", i_as_str), val); +				} else { +					new_string = new_string.replace_first(placeholder, val); +				} +			} +		} +	} else if (values.get_type() == Variant::DICTIONARY) { +		Dictionary d = values; +		List<Variant> keys; +		d.get_key_list(&keys); + +		for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { +			String key = E->get(); +			String val = d[E->get()]; + +			if (key.left(1) == "\"" && key.right(key.length() - 1) == "\"") { +				key = key.substr(1, key.length() - 2); +			} + +			if (val.left(1) == "\"" && val.right(val.length() - 1) == "\"") { +				val = val.substr(1, val.length() - 2); +			} + +			new_string = new_string.replace(placeholder.replace("_", key), val); +		} +	} else { +		ERR_PRINT(String("Invalid type: use Array or Dictionary.").ascii().get_data()); +	} + +	return new_string; +} + +String String::replace(const String &p_key, const String &p_with) const { +	String new_string; +	int search_from = 0; +	int result = 0; + +	while ((result = find(p_key, search_from)) >= 0) { +		new_string += substr(search_from, result - search_from); +		new_string += p_with; +		search_from = result + p_key.length(); +	} + +	if (search_from == 0) { +		return *this; +	} + +	new_string += substr(search_from, length() - search_from); + +	return new_string; +} + +String String::replace(const char *p_key, const char *p_with) const { +	String new_string; +	int search_from = 0; +	int result = 0; + +	while ((result = find(p_key, search_from)) >= 0) { +		new_string += substr(search_from, result - search_from); +		new_string += p_with; +		int k = 0; +		while (p_key[k] != '\0') { +			k++; +		} +		search_from = result + k; +	} + +	if (search_from == 0) { +		return *this; +	} + +	new_string += substr(search_from, length() - search_from); + +	return new_string; +} + +String String::replace_first(const String &p_key, const String &p_with) const { +	int pos = find(p_key); +	if (pos >= 0) { +		return substr(0, pos) + p_with + substr(pos + p_key.length(), length()); +	} + +	return *this; +} + +String String::replacen(const String &p_key, const String &p_with) const { +	String new_string; +	int search_from = 0; +	int result = 0; + +	while ((result = findn(p_key, search_from)) >= 0) { +		new_string += substr(search_from, result - search_from); +		new_string += p_with; +		search_from = result + p_key.length(); +	} + +	if (search_from == 0) { +		return *this; +	} + +	new_string += substr(search_from, length() - search_from); +	return new_string; +} + +String String::repeat(int p_count) const { +	ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number."); + +	String new_string; +	const char32_t *src = this->get_data(); + +	new_string.resize(length() * p_count + 1); +	new_string[length() * p_count] = 0; + +	for (int i = 0; i < p_count; i++) { +		for (int j = 0; j < length(); j++) { +			new_string[i * length() + j] = src[j]; +		} +	} + +	return new_string; +} + +String String::left(int p_pos) const { +	if (p_pos <= 0) { +		return ""; +	} + +	if (p_pos >= length()) { +		return *this; +	} + +	return substr(0, p_pos); +} + +String String::right(int p_pos) const { +	if (p_pos >= length()) { +		return ""; +	} + +	if (p_pos <= 0) { +		return *this; +	} + +	return substr(p_pos, (length() - p_pos)); +} + +char32_t String::ord_at(int p_idx) const { +	ERR_FAIL_INDEX_V(p_idx, length(), 0); +	return operator[](p_idx); +} + +String String::dedent() const { +	String new_string; +	String indent; +	bool has_indent = false; +	bool has_text = false; +	int line_start = 0; +	int indent_stop = -1; + +	for (int i = 0; i < length(); i++) { +		char32_t c = operator[](i); +		if (c == '\n') { +			if (has_text) { +				new_string += substr(indent_stop, i - indent_stop); +			} +			new_string += "\n"; +			has_text = false; +			line_start = i + 1; +			indent_stop = -1; +		} else if (!has_text) { +			if (c > 32) { +				has_text = true; +				if (!has_indent) { +					has_indent = true; +					indent = substr(line_start, i - line_start); +					indent_stop = i; +				} +			} +			if (has_indent && indent_stop < 0) { +				int j = i - line_start; +				if (j >= indent.length() || c != indent[j]) { +					indent_stop = i; +				} +			} +		} +	} + +	if (has_text) { +		new_string += substr(indent_stop, length() - indent_stop); +	} + +	return new_string; +} + +String String::strip_edges(bool left, bool right) const { +	int len = length(); +	int beg = 0, end = len; + +	if (left) { +		for (int i = 0; i < len; i++) { +			if (operator[](i) <= 32) { +				beg++; +			} else { +				break; +			} +		} +	} + +	if (right) { +		for (int i = (int)(len - 1); i >= 0; i--) { +			if (operator[](i) <= 32) { +				end--; +			} else { +				break; +			} +		} +	} + +	if (beg == 0 && end == len) { +		return *this; +	} + +	return substr(beg, end - beg); +} + +String String::strip_escapes() const { +	String new_string; +	for (int i = 0; i < length(); i++) { +		// Escape characters on first page of the ASCII table, before 32 (Space). +		if (operator[](i) < 32) { +			continue; +		} +		new_string += operator[](i); +	} + +	return new_string; +} + +String String::lstrip(const String &p_chars) const { +	int len = length(); +	int beg; + +	for (beg = 0; beg < len; beg++) { +		if (p_chars.find_char(get(beg)) == -1) { +			break; +		} +	} + +	if (beg == 0) { +		return *this; +	} + +	return substr(beg, len - beg); +} + +String String::rstrip(const String &p_chars) const { +	int len = length(); +	int end; + +	for (end = len - 1; end >= 0; end--) { +		if (p_chars.find_char(get(end)) == -1) { +			break; +		} +	} + +	if (end == len - 1) { +		return *this; +	} + +	return substr(0, end + 1); +} + +String String::simplify_path() const { +	String s = *this; +	String drive; +	if (s.begins_with("local://")) { +		drive = "local://"; +		s = s.substr(8, s.length()); +	} else if (s.begins_with("res://")) { +		drive = "res://"; +		s = s.substr(6, s.length()); +	} else if (s.begins_with("user://")) { +		drive = "user://"; +		s = s.substr(7, s.length()); +	} else if (s.begins_with("/") || s.begins_with("\\")) { +		drive = s.substr(0, 1); +		s = s.substr(1, s.length() - 1); +	} else { +		int p = s.find(":/"); +		if (p == -1) { +			p = s.find(":\\"); +		} +		if (p != -1 && p < s.find("/")) { +			drive = s.substr(0, p + 2); +			s = s.substr(p + 2, s.length()); +		} +	} + +	s = s.replace("\\", "/"); +	while (true) { // in case of using 2 or more slash +		String compare = s.replace("//", "/"); +		if (s == compare) { +			break; +		} else { +			s = compare; +		} +	} +	Vector<String> dirs = s.split("/", false); + +	for (int i = 0; i < dirs.size(); i++) { +		String d = dirs[i]; +		if (d == ".") { +			dirs.remove(i); +			i--; +		} else if (d == "..") { +			if (i == 0) { +				dirs.remove(i); +				i--; +			} else { +				dirs.remove(i); +				dirs.remove(i - 1); +				i -= 2; +			} +		} +	} + +	s = ""; + +	for (int i = 0; i < dirs.size(); i++) { +		if (i > 0) { +			s += "/"; +		} +		s += dirs[i]; +	} + +	return drive + s; +} + +static int _humanize_digits(int p_num) { +	if (p_num < 100) { +		return 2; +	} else if (p_num < 1024) { +		return 1; +	} else { +		return 0; +	} +} + +String String::humanize_size(uint64_t p_size) { +	uint64_t _div = 1; +	Vector<String> prefixes; +	prefixes.push_back(RTR("B")); +	prefixes.push_back(RTR("KiB")); +	prefixes.push_back(RTR("MiB")); +	prefixes.push_back(RTR("GiB")); +	prefixes.push_back(RTR("TiB")); +	prefixes.push_back(RTR("PiB")); +	prefixes.push_back(RTR("EiB")); + +	int prefix_idx = 0; + +	while (prefix_idx < prefixes.size() - 1 && p_size > (_div * 1024)) { +		_div *= 1024; +		prefix_idx++; +	} + +	const int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0; +	const double divisor = prefix_idx > 0 ? _div : 1; + +	return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx]; +} + +bool String::is_abs_path() const { +	if (length() > 1) { +		return (operator[](0) == '/' || operator[](0) == '\\' || find(":/") != -1 || find(":\\") != -1); +	} else if ((length()) == 1) { +		return (operator[](0) == '/' || operator[](0) == '\\'); +	} else { +		return false; +	} +} + +bool String::is_valid_identifier() const { +	int len = length(); + +	if (len == 0) { +		return false; +	} + +	const char32_t *str = &operator[](0); + +	for (int i = 0; i < len; i++) { +		if (i == 0) { +			if (str[0] >= '0' && str[0] <= '9') { +				return false; // no start with number plz +			} +		} + +		bool valid_char = (str[i] >= '0' && str[i] <= '9') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z') || str[i] == '_'; + +		if (!valid_char) { +			return false; +		} +	} + +	return true; +} + +bool String::is_valid_string() const { +	int l = length(); +	const char32_t *src = get_data(); +	bool valid = true; +	for (int i = 0; i < l; i++) { +		valid = valid && (src[i] < 0xd800 || (src[i] > 0xdfff && src[i] <= 0x10ffff)); +	} +	return valid; +} + +String String::http_escape() const { +	const CharString temp = utf8(); +	String res; +	for (int i = 0; i < temp.length(); ++i) { +		char ord = temp[i]; +		if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || +				(ord >= 'a' && ord <= 'z') || +				(ord >= 'A' && ord <= 'Z') || +				(ord >= '0' && ord <= '9')) { +			res += ord; +		} else { +			char h_Val[3]; +#if defined(__GNUC__) || defined(_MSC_VER) +			snprintf(h_Val, 3, "%hhX", ord); +#else +			sprintf(h_Val, "%hhX", ord); +#endif +			res += "%"; +			res += h_Val; +		} +	} +	return res; +} + +String String::http_unescape() const { +	String res; +	for (int i = 0; i < length(); ++i) { +		if (ord_at(i) == '%' && i + 2 < length()) { +			char32_t ord1 = ord_at(i + 1); +			if ((ord1 >= '0' && ord1 <= '9') || (ord1 >= 'A' && ord1 <= 'Z')) { +				char32_t ord2 = ord_at(i + 2); +				if ((ord2 >= '0' && ord2 <= '9') || (ord2 >= 'A' && ord2 <= 'Z')) { +					char bytes[3] = { (char)ord1, (char)ord2, 0 }; +					res += (char)strtol(bytes, nullptr, 16); +					i += 2; +				} +			} else { +				res += ord_at(i); +			} +		} else { +			res += ord_at(i); +		} +	} +	return String::utf8(res.ascii()); +} + +String String::c_unescape() const { +	String escaped = *this; +	escaped = escaped.replace("\\a", "\a"); +	escaped = escaped.replace("\\b", "\b"); +	escaped = escaped.replace("\\f", "\f"); +	escaped = escaped.replace("\\n", "\n"); +	escaped = escaped.replace("\\r", "\r"); +	escaped = escaped.replace("\\t", "\t"); +	escaped = escaped.replace("\\v", "\v"); +	escaped = escaped.replace("\\'", "\'"); +	escaped = escaped.replace("\\\"", "\""); +	escaped = escaped.replace("\\?", "\?"); +	escaped = escaped.replace("\\\\", "\\"); + +	return escaped; +} + +String String::c_escape() const { +	String escaped = *this; +	escaped = escaped.replace("\\", "\\\\"); +	escaped = escaped.replace("\a", "\\a"); +	escaped = escaped.replace("\b", "\\b"); +	escaped = escaped.replace("\f", "\\f"); +	escaped = escaped.replace("\n", "\\n"); +	escaped = escaped.replace("\r", "\\r"); +	escaped = escaped.replace("\t", "\\t"); +	escaped = escaped.replace("\v", "\\v"); +	escaped = escaped.replace("\'", "\\'"); +	escaped = escaped.replace("\?", "\\?"); +	escaped = escaped.replace("\"", "\\\""); + +	return escaped; +} + +String String::c_escape_multiline() const { +	String escaped = *this; +	escaped = escaped.replace("\\", "\\\\"); +	escaped = escaped.replace("\"", "\\\""); + +	return escaped; +} + +String String::json_escape() const { +	String escaped = *this; +	escaped = escaped.replace("\\", "\\\\"); +	escaped = escaped.replace("\b", "\\b"); +	escaped = escaped.replace("\f", "\\f"); +	escaped = escaped.replace("\n", "\\n"); +	escaped = escaped.replace("\r", "\\r"); +	escaped = escaped.replace("\t", "\\t"); +	escaped = escaped.replace("\v", "\\v"); +	escaped = escaped.replace("\"", "\\\""); + +	return escaped; +} + +String String::xml_escape(bool p_escape_quotes) const { +	String str = *this; +	str = str.replace("&", "&"); +	str = str.replace("<", "<"); +	str = str.replace(">", ">"); +	if (p_escape_quotes) { +		str = str.replace("'", "'"); +		str = str.replace("\"", """); +	} +	/* +for (int i=1;i<32;i++) { +	char chr[2]={i,0}; +	str=str.replace(chr,"&#"+String::num(i)+";"); +}*/ +	return str; +} + +static _FORCE_INLINE_ int _xml_unescape(const char32_t *p_src, int p_src_len, char32_t *p_dst) { +	int len = 0; +	while (p_src_len) { +		if (*p_src == '&') { +			int eat = 0; + +			if (p_src_len >= 4 && p_src[1] == '#') { +				char32_t c = 0; + +				for (int i = 2; i < p_src_len; i++) { +					eat = i + 1; +					char32_t ct = p_src[i]; +					if (ct == ';') { +						break; +					} else if (ct >= '0' && ct <= '9') { +						ct = ct - '0'; +					} else if (ct >= 'a' && ct <= 'f') { +						ct = (ct - 'a') + 10; +					} else if (ct >= 'A' && ct <= 'F') { +						ct = (ct - 'A') + 10; +					} else { +						continue; +					} +					c <<= 4; +					c |= ct; +				} + +				if (p_dst) { +					*p_dst = c; +				} + +			} else if (p_src_len >= 4 && p_src[1] == 'g' && p_src[2] == 't' && p_src[3] == ';') { +				if (p_dst) { +					*p_dst = '>'; +				} +				eat = 4; +			} else if (p_src_len >= 4 && p_src[1] == 'l' && p_src[2] == 't' && p_src[3] == ';') { +				if (p_dst) { +					*p_dst = '<'; +				} +				eat = 4; +			} else if (p_src_len >= 5 && p_src[1] == 'a' && p_src[2] == 'm' && p_src[3] == 'p' && p_src[4] == ';') { +				if (p_dst) { +					*p_dst = '&'; +				} +				eat = 5; +			} else if (p_src_len >= 6 && p_src[1] == 'q' && p_src[2] == 'u' && p_src[3] == 'o' && p_src[4] == 't' && p_src[5] == ';') { +				if (p_dst) { +					*p_dst = '"'; +				} +				eat = 6; +			} else if (p_src_len >= 6 && p_src[1] == 'a' && p_src[2] == 'p' && p_src[3] == 'o' && p_src[4] == 's' && p_src[5] == ';') { +				if (p_dst) { +					*p_dst = '\''; +				} +				eat = 6; +			} else { +				if (p_dst) { +					*p_dst = *p_src; +				} +				eat = 1; +			} + +			if (p_dst) { +				p_dst++; +			} + +			len++; +			p_src += eat; +			p_src_len -= eat; +		} else { +			if (p_dst) { +				*p_dst = *p_src; +				p_dst++; +			} +			len++; +			p_src++; +			p_src_len--; +		} +	} + +	return len; +} + +String String::xml_unescape() const { +	String str; +	int l = length(); +	int len = _xml_unescape(get_data(), l, nullptr); +	if (len == 0) { +		return String(); +	} +	str.resize(len + 1); +	_xml_unescape(get_data(), l, str.ptrw()); +	str[len] = 0; +	return str; +} + +String String::pad_decimals(int p_digits) const { +	String s = *this; +	int c = s.find("."); + +	if (c == -1) { +		if (p_digits <= 0) { +			return s; +		} +		s += "."; +		c = s.length() - 1; +	} else { +		if (p_digits <= 0) { +			return s.substr(0, c); +		} +	} + +	if (s.length() - (c + 1) > p_digits) { +		s = s.substr(0, c + p_digits + 1); +	} else { +		while (s.length() - (c + 1) < p_digits) { +			s += "0"; +		} +	} +	return s; +} + +String String::pad_zeros(int p_digits) const { +	String s = *this; +	int end = s.find("."); + +	if (end == -1) { +		end = s.length(); +	} + +	if (end == 0) { +		return s; +	} + +	int begin = 0; + +	while (begin < end && (s[begin] < '0' || s[begin] > '9')) { +		begin++; +	} + +	if (begin >= end) { +		return s; +	} + +	while (end - begin < p_digits) { +		s = s.insert(begin, "0"); +		end++; +	} + +	return s; +} + +String String::trim_prefix(const String &p_prefix) const { +	String s = *this; +	if (s.begins_with(p_prefix)) { +		return s.substr(p_prefix.length(), s.length() - p_prefix.length()); +	} +	return s; +} + +String String::trim_suffix(const String &p_suffix) const { +	String s = *this; +	if (s.ends_with(p_suffix)) { +		return s.substr(0, s.length() - p_suffix.length()); +	} +	return s; +} + +bool String::is_valid_integer() const { +	int len = length(); + +	if (len == 0) { +		return false; +	} + +	int from = 0; +	if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { +		from++; +	} + +	for (int i = from; i < len; i++) { +		if (operator[](i) < '0' || operator[](i) > '9') { +			return false; // no start with number plz +		} +	} + +	return true; +} + +bool String::is_valid_hex_number(bool p_with_prefix) const { +	int len = length(); + +	if (len == 0) { +		return false; +	} + +	int from = 0; +	if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { +		from++; +	} + +	if (p_with_prefix) { +		if (len < 3) { +			return false; +		} +		if (operator[](from) != '0' || operator[](from + 1) != 'x') { +			return false; +		} +		from += 2; +	} + +	for (int i = from; i < len; i++) { +		char32_t c = operator[](i); +		if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { +			continue; +		} +		return false; +	} + +	return true; +} + +bool String::is_valid_float() const { +	int len = length(); + +	if (len == 0) { +		return false; +	} + +	int from = 0; +	if (operator[](0) == '+' || operator[](0) == '-') { +		from++; +	} + +	bool exponent_found = false; +	bool period_found = false; +	bool sign_found = false; +	bool exponent_values_found = false; +	bool numbers_found = false; + +	for (int i = from; i < len; i++) { +		if (operator[](i) >= '0' && operator[](i) <= '9') { +			if (exponent_found) { +				exponent_values_found = true; +			} else { +				numbers_found = true; +			} +		} else if (numbers_found && !exponent_found && operator[](i) == 'e') { +			exponent_found = true; +		} else if (!period_found && !exponent_found && operator[](i) == '.') { +			period_found = true; +		} else if ((operator[](i) == '-' || operator[](i) == '+') && exponent_found && !exponent_values_found && !sign_found) { +			sign_found = true; +		} else { +			return false; // no start with number plz +		} +	} + +	return numbers_found; +} + +String String::path_to_file(const String &p_path) const { +	// Don't get base dir for src, this is expected to be a dir already. +	String src = this->replace("\\", "/"); +	String dst = p_path.replace("\\", "/").get_base_dir(); +	String rel = src.path_to(dst); +	if (rel == dst) { // failed +		return p_path; +	} else { +		return rel + p_path.get_file(); +	} +} + +String String::path_to(const String &p_path) const { +	String src = this->replace("\\", "/"); +	String dst = p_path.replace("\\", "/"); +	if (!src.ends_with("/")) { +		src += "/"; +	} +	if (!dst.ends_with("/")) { +		dst += "/"; +	} + +	String base; + +	if (src.begins_with("res://") && dst.begins_with("res://")) { +		base = "res:/"; +		src = src.replace("res://", "/"); +		dst = dst.replace("res://", "/"); + +	} else if (src.begins_with("user://") && dst.begins_with("user://")) { +		base = "user:/"; +		src = src.replace("user://", "/"); +		dst = dst.replace("user://", "/"); + +	} else if (src.begins_with("/") && dst.begins_with("/")) { +		//nothing +	} else { +		//dos style +		String src_begin = src.get_slicec('/', 0); +		String dst_begin = dst.get_slicec('/', 0); + +		if (src_begin != dst_begin) { +			return p_path; //impossible to do this +		} + +		base = src_begin; +		src = src.substr(src_begin.length(), src.length()); +		dst = dst.substr(dst_begin.length(), dst.length()); +	} + +	//remove leading and trailing slash and split +	Vector<String> src_dirs = src.substr(1, src.length() - 2).split("/"); +	Vector<String> dst_dirs = dst.substr(1, dst.length() - 2).split("/"); + +	//find common parent +	int common_parent = 0; + +	while (true) { +		if (src_dirs.size() == common_parent) { +			break; +		} +		if (dst_dirs.size() == common_parent) { +			break; +		} +		if (src_dirs[common_parent] != dst_dirs[common_parent]) { +			break; +		} +		common_parent++; +	} + +	common_parent--; + +	String dir; + +	for (int i = src_dirs.size() - 1; i > common_parent; i--) { +		dir += "../"; +	} + +	for (int i = common_parent + 1; i < dst_dirs.size(); i++) { +		dir += dst_dirs[i] + "/"; +	} + +	if (dir.length() == 0) { +		dir = "./"; +	} +	return dir; +} + +bool String::is_valid_html_color() const { +	return Color::html_is_valid(*this); +} + +bool String::is_valid_filename() const { +	String stripped = strip_edges(); +	if (*this != stripped) { +		return false; +	} + +	if (stripped == String()) { +		return false; +	} + +	return !(find(":") != -1 || find("/") != -1 || find("\\") != -1 || find("?") != -1 || find("*") != -1 || find("\"") != -1 || find("|") != -1 || find("%") != -1 || find("<") != -1 || find(">") != -1); +} + +bool String::is_valid_ip_address() const { +	if (find(":") >= 0) { +		Vector<String> ip = split(":"); +		for (int i = 0; i < ip.size(); i++) { +			String n = ip[i]; +			if (n.empty()) { +				continue; +			} +			if (n.is_valid_hex_number(false)) { +				int64_t nint = n.hex_to_int(false); +				if (nint < 0 || nint > 0xffff) { +					return false; +				} +				continue; +			} +			if (!n.is_valid_ip_address()) { +				return false; +			} +		} + +	} else { +		Vector<String> ip = split("."); +		if (ip.size() != 4) { +			return false; +		} +		for (int i = 0; i < ip.size(); i++) { +			String n = ip[i]; +			if (!n.is_valid_integer()) { +				return false; +			} +			int val = n.to_int(); +			if (val < 0 || val > 255) { +				return false; +			} +		} +	} + +	return true; +} + +bool String::is_resource_file() const { +	return begins_with("res://") && find("::") == -1; +} + +bool String::is_rel_path() const { +	return !is_abs_path(); +} + +String String::get_base_dir() const { +	int basepos = find("://"); +	String rs; +	String base; +	if (basepos != -1) { +		int end = basepos + 3; +		rs = substr(end, length()); +		base = substr(0, end); +	} else { +		if (begins_with("/")) { +			rs = substr(1, length()); +			base = "/"; +		} else { +			rs = *this; +		} +	} + +	int sep = MAX(rs.rfind("/"), rs.rfind("\\")); +	if (sep == -1) { +		return base; +	} + +	return base + rs.substr(0, sep); +} + +String String::get_file() const { +	int sep = MAX(rfind("/"), rfind("\\")); +	if (sep == -1) { +		return *this; +	} + +	return substr(sep + 1, length()); +} + +String String::get_extension() const { +	int pos = rfind("."); +	if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { +		return ""; +	} + +	return substr(pos + 1, length()); +} + +String String::plus_file(const String &p_file) const { +	if (empty()) { +		return p_file; +	} +	if (operator[](length() - 1) == '/' || (p_file.size() > 0 && p_file.operator[](0) == '/')) { +		return *this + p_file; +	} +	return *this + "/" + p_file; +} + +String String::percent_encode() const { +	CharString cs = utf8(); +	String encoded; +	for (int i = 0; i < cs.length(); i++) { +		uint8_t c = cs[i]; +		if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '~' || c == '.') { +			char p[2] = { (char)c, 0 }; +			encoded += p; +		} else { +			char p[4] = { '%', 0, 0, 0 }; +			static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +			p[1] = hex[c >> 4]; +			p[2] = hex[c & 0xF]; +			encoded += p; +		} +	} + +	return encoded; +} + +String String::percent_decode() const { +	CharString pe; + +	CharString cs = utf8(); +	for (int i = 0; i < cs.length(); i++) { +		uint8_t c = cs[i]; +		if (c == '%' && i < length() - 2) { +			uint8_t a = LOWERCASE(cs[i + 1]); +			uint8_t b = LOWERCASE(cs[i + 2]); + +			if (a >= '0' && a <= '9') { +				c = (a - '0') << 4; +			} else if (a >= 'a' && a <= 'f') { +				c = (a - 'a' + 10) << 4; +			} else { +				continue; +			} + +			uint8_t d = 0; + +			if (b >= '0' && b <= '9') { +				d = (b - '0'); +			} else if (b >= 'a' && b <= 'f') { +				d = (b - 'a' + 10); +			} else { +				continue; +			} +			c += d; +			i += 2; +		} +		pe += c; +	} + +	return String::utf8(pe.ptr()); +} + +String String::property_name_encode() const { +	// Escape and quote strings with extended ASCII or further Unicode characters +	// as well as '"', '=' or ' ' (32) +	const char32_t *cstr = get_data(); +	for (int i = 0; cstr[i]; i++) { +		if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] < 33 || cstr[i] > 126) { +			return "\"" + c_escape_multiline() + "\""; +		} +	} +	// Keep as is +	return *this; +} + +String String::get_basename() const { +	int pos = rfind("."); +	if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { +		return *this; +	} + +	return substr(0, pos); +} + +String itos(int64_t p_val) { +	return String::num_int64(p_val); +} + +String uitos(uint64_t p_val) { +	return String::num_uint64(p_val); +} + +String rtos(double p_val) { +	return String::num(p_val); +} + +String rtoss(double p_val) { +	return String::num_scientific(p_val); +} + +// Right-pad with a character. +String String::rpad(int min_length, const String &character) const { +	String s = *this; +	int padding = min_length - s.length(); +	if (padding > 0) { +		for (int i = 0; i < padding; i++) { +			s = s + character; +		} +	} + +	return s; +} + +// Left-pad with a character. +String String::lpad(int min_length, const String &character) const { +	String s = *this; +	int padding = min_length - s.length(); +	if (padding > 0) { +		for (int i = 0; i < padding; i++) { +			s = character + s; +		} +	} + +	return s; +} + +// sprintf is implemented in GDScript via: +//   "fish %s pie" % "frog" +//   "fish %s %d pie" % ["frog", 12] +// In case of an error, the string returned is the error description and "error" is true. +String String::sprintf(const Array &values, bool *error) const { +	String formatted; +	char32_t *self = (char32_t *)get_data(); +	bool in_format = false; +	int value_index = 0; +	int min_chars = 0; +	int min_decimals = 0; +	bool in_decimals = false; +	bool pad_with_zeroes = false; +	bool left_justified = false; +	bool show_sign = false; + +	if (error) { +		*error = true; +	} + +	for (; *self; self++) { +		const char32_t c = *self; + +		if (in_format) { // We have % - lets see what else we get. +			switch (c) { +				case '%': { // Replace %% with % +					formatted += chr(c); +					in_format = false; +					break; +				} +				case 'd': // Integer (signed) +				case 'o': // Octal +				case 'x': // Hexadecimal (lowercase) +				case 'X': { // Hexadecimal (uppercase) +					if (value_index >= values.size()) { +						return "not enough arguments for format string"; +					} + +					if (!values[value_index].is_num()) { +						return "a number is required"; +					} + +					int64_t value = values[value_index]; +					int base = 16; +					bool capitalize = false; +					switch (c) { +						case 'd': +							base = 10; +							break; +						case 'o': +							base = 8; +							break; +						case 'x': +							break; +						case 'X': +							base = 16; +							capitalize = true; +							break; +					} +					// Get basic number. +					String str = String::num_int64(ABS(value), base, capitalize); +					int number_len = str.length(); + +					// Padding. +					int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars; +					String pad_char = pad_with_zeroes ? String("0") : String(" "); +					if (left_justified) { +						str = str.rpad(pad_chars_count, pad_char); +					} else { +						str = str.lpad(pad_chars_count, pad_char); +					} + +					// Sign. +					if (show_sign && value >= 0) { +						str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "+"); +					} else if (value < 0) { +						str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "-"); +					} + +					formatted += str; +					++value_index; +					in_format = false; + +					break; +				} +				case 'f': { // Float +					if (value_index >= values.size()) { +						return "not enough arguments for format string"; +					} + +					if (!values[value_index].is_num()) { +						return "a number is required"; +					} + +					double value = values[value_index]; +					bool is_negative = (value < 0); +					String str = String::num(ABS(value), min_decimals); + +					// Pad decimals out. +					str = str.pad_decimals(min_decimals); + +					int initial_len = str.length(); + +					// Padding. Leave room for sign later if required. +					int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars; +					String pad_char = pad_with_zeroes ? String("0") : String(" "); +					if (left_justified) { +						if (pad_with_zeroes) { +							return "left justification cannot be used with zeros as the padding"; +						} else { +							str = str.rpad(pad_chars_count, pad_char); +						} +					} else { +						str = str.lpad(pad_chars_count, pad_char); +					} + +					// Add sign if needed. +					if (show_sign || is_negative) { +						String sign_char = is_negative ? "-" : "+"; +						if (left_justified) { +							str = str.insert(0, sign_char); +						} else { +							str = str.insert(pad_with_zeroes ? 0 : str.length() - initial_len, sign_char); +						} +					} + +					formatted += str; +					++value_index; +					in_format = false; +					break; +				} +				case 's': { // String +					if (value_index >= values.size()) { +						return "not enough arguments for format string"; +					} + +					String str = values[value_index]; +					// Padding. +					if (left_justified) { +						str = str.rpad(min_chars); +					} else { +						str = str.lpad(min_chars); +					} + +					formatted += str; +					++value_index; +					in_format = false; +					break; +				} +				case 'c': { +					if (value_index >= values.size()) { +						return "not enough arguments for format string"; +					} + +					// Convert to character. +					String str; +					if (values[value_index].is_num()) { +						int value = values[value_index]; +						if (value < 0) { +							return "unsigned integer is lower than minimum"; +						} else if (value >= 0xd800 && value <= 0xdfff) { +							return "unsigned integer is invalid Unicode character"; +						} else if (value > 0x10ffff) { +							return "unsigned integer is greater than maximum"; +						} +						str = chr(values[value_index]); +					} else if (values[value_index].get_type() == Variant::STRING) { +						str = values[value_index]; +						if (str.length() != 1) { +							return "%c requires number or single-character string"; +						} +					} else { +						return "%c requires number or single-character string"; +					} + +					// Padding. +					if (left_justified) { +						str = str.rpad(min_chars); +					} else { +						str = str.lpad(min_chars); +					} + +					formatted += str; +					++value_index; +					in_format = false; +					break; +				} +				case '-': { // Left justify +					left_justified = true; +					break; +				} +				case '+': { // Show + if positive. +					show_sign = true; +					break; +				} +				case '0': +				case '1': +				case '2': +				case '3': +				case '4': +				case '5': +				case '6': +				case '7': +				case '8': +				case '9': { +					int n = c - '0'; +					if (in_decimals) { +						min_decimals *= 10; +						min_decimals += n; +					} else { +						if (c == '0' && min_chars == 0) { +							pad_with_zeroes = true; +						} else { +							min_chars *= 10; +							min_chars += n; +						} +					} +					break; +				} +				case '.': { // Float separator. +					if (in_decimals) { +						return "too many decimal points in format"; +					} +					in_decimals = true; +					min_decimals = 0; // We want to add the value manually. +					break; +				} + +				case '*': { // Dynamic width, based on value. +					if (value_index >= values.size()) { +						return "not enough arguments for format string"; +					} + +					if (!values[value_index].is_num()) { +						return "* wants number"; +					} + +					int size = values[value_index]; + +					if (in_decimals) { +						min_decimals = size; +					} else { +						min_chars = size; +					} + +					++value_index; +					break; +				} + +				default: { +					return "unsupported format character"; +				} +			} +		} else { // Not in format string. +			switch (c) { +				case '%': +					in_format = true; +					// Back to defaults: +					min_chars = 0; +					min_decimals = 6; +					pad_with_zeroes = false; +					left_justified = false; +					show_sign = false; +					in_decimals = false; +					break; +				default: +					formatted += chr(c); +			} +		} +	} + +	if (in_format) { +		return "incomplete format"; +	} + +	if (value_index != values.size()) { +		return "not all arguments converted during string formatting"; +	} + +	if (error) { +		*error = false; +	} +	return formatted; +} + +String String::quote(String quotechar) const { +	return quotechar + *this + quotechar; +} + +String String::unquote() const { +	if (!is_quoted()) { +		return *this; +	} + +	return substr(1, length() - 2); +} + +Vector<uint8_t> String::to_ascii_buffer() const { +	const String *s = this; +	if (s->empty()) { +		return Vector<uint8_t>(); +	} +	CharString charstr = s->ascii(); + +	Vector<uint8_t> retval; +	size_t len = charstr.length(); +	retval.resize(len); +	uint8_t *w = retval.ptrw(); +	copymem(w, charstr.ptr(), len); + +	return retval; +} + +Vector<uint8_t> String::to_utf8_buffer() const { +	const String *s = this; +	if (s->empty()) { +		return Vector<uint8_t>(); +	} +	CharString charstr = s->utf8(); + +	Vector<uint8_t> retval; +	size_t len = charstr.length(); +	retval.resize(len); +	uint8_t *w = retval.ptrw(); +	copymem(w, charstr.ptr(), len); + +	return retval; +} + +Vector<uint8_t> String::to_utf16_buffer() const { +	const String *s = this; +	if (s->empty()) { +		return Vector<uint8_t>(); +	} +	Char16String charstr = s->utf16(); + +	Vector<uint8_t> retval; +	size_t len = charstr.length() * 2; +	retval.resize(len); +	uint8_t *w = retval.ptrw(); +	copymem(w, (const void *)charstr.ptr(), len); + +	return retval; +} + +Vector<uint8_t> String::to_utf32_buffer() const { +	const String *s = this; +	if (s->empty()) { +		return Vector<uint8_t>(); +	} + +	Vector<uint8_t> retval; +	size_t len = s->length() * 4; +	retval.resize(len); +	uint8_t *w = retval.ptrw(); +	copymem(w, (const void *)s->ptr(), len); + +	return retval; +} + +#ifdef TOOLS_ENABLED +String TTR(const String &p_text, const String &p_context) { +	if (TranslationServer::get_singleton()) { +		return TranslationServer::get_singleton()->tool_translate(p_text, p_context); +	} + +	return p_text; +} + +String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) { +	if (TranslationServer::get_singleton()) { +		return TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context); +	} + +	// Return message based on English plural rule if translation is not possible. +	if (p_n == 1) { +		return p_text; +	} +	return p_text_plural; +} + +String DTR(const String &p_text, const String &p_context) { +	// Comes straight from the XML, so remove indentation and any trailing whitespace. +	const String text = p_text.dedent().strip_edges(); + +	if (TranslationServer::get_singleton()) { +		return TranslationServer::get_singleton()->doc_translate(text, p_context); +	} + +	return text; +} + +String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) { +	const String text = p_text.dedent().strip_edges(); +	const String text_plural = p_text_plural.dedent().strip_edges(); + +	if (TranslationServer::get_singleton()) { +		return TranslationServer::get_singleton()->doc_translate_plural(text, text_plural, p_n, p_context); +	} + +	// Return message based on English plural rule if translation is not possible. +	if (p_n == 1) { +		return text; +	} +	return text_plural; +} +#endif + +String RTR(const String &p_text, const String &p_context) { +	if (TranslationServer::get_singleton()) { +		String rtr = TranslationServer::get_singleton()->tool_translate(p_text, p_context); +		if (rtr == String() || rtr == p_text) { +			return TranslationServer::get_singleton()->translate(p_text, p_context); +		} else { +			return rtr; +		} +	} + +	return p_text; +} + +String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) { +	if (TranslationServer::get_singleton()) { +		String rtr = TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context); +		if (rtr == String() || rtr == p_text || rtr == p_text_plural) { +			return TranslationServer::get_singleton()->translate_plural(p_text, p_text_plural, p_n, p_context); +		} else { +			return rtr; +		} +	} + +	// Return message based on English plural rule if translation is not possible. +	if (p_n == 1) { +		return p_text; +	} +	return p_text_plural; +} diff --git a/core/string/ustring.h b/core/string/ustring.h new file mode 100644 index 0000000000..bfae16fe64 --- /dev/null +++ b/core/string/ustring.h @@ -0,0 +1,558 @@ +/*************************************************************************/ +/*  ustring.h                                                            */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef USTRING_H +#define USTRING_H + +#include "core/templates/cowdata.h" +#include "core/templates/vector.h" +#include "core/typedefs.h" +#include "core/variant/array.h" + +/*************************************************************************/ +/*  CharProxy                                                            */ +/*************************************************************************/ + +template <class T> +class CharProxy { +	friend class Char16String; +	friend class CharString; +	friend class String; + +	const int _index; +	CowData<T> &_cowdata; +	static const T _null = 0; + +	_FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &cowdata) : +			_index(p_index), +			_cowdata(cowdata) {} + +public: +	_FORCE_INLINE_ operator T() const { +		if (unlikely(_index == _cowdata.size())) { +			return _null; +		} + +		return _cowdata.get(_index); +	} + +	_FORCE_INLINE_ const T *operator&() const { +		return _cowdata.ptr() + _index; +	} + +	_FORCE_INLINE_ void operator=(const T &other) const { +		_cowdata.set(_index, other); +	} + +	_FORCE_INLINE_ void operator=(const CharProxy<T> &other) const { +		_cowdata.set(_index, other.operator T()); +	} +}; + +/*************************************************************************/ +/*  Char16String                                                         */ +/*************************************************************************/ + +class Char16String { +	CowData<char16_t> _cowdata; +	static const char16_t _null; + +public: +	_FORCE_INLINE_ char16_t *ptrw() { return _cowdata.ptrw(); } +	_FORCE_INLINE_ const char16_t *ptr() const { return _cowdata.ptr(); } +	_FORCE_INLINE_ int size() const { return _cowdata.size(); } +	Error resize(int p_size) { return _cowdata.resize(p_size); } + +	_FORCE_INLINE_ char16_t get(int p_index) const { return _cowdata.get(p_index); } +	_FORCE_INLINE_ void set(int p_index, const char16_t &p_elem) { _cowdata.set(p_index, p_elem); } +	_FORCE_INLINE_ const char16_t &operator[](int p_index) const { +		if (unlikely(p_index == _cowdata.size())) { +			return _null; +		} + +		return _cowdata.get(p_index); +	} +	_FORCE_INLINE_ CharProxy<char16_t> operator[](int p_index) { return CharProxy<char16_t>(p_index, _cowdata); } + +	_FORCE_INLINE_ Char16String() {} +	_FORCE_INLINE_ Char16String(const Char16String &p_str) { _cowdata._ref(p_str._cowdata); } +	_FORCE_INLINE_ Char16String &operator=(const Char16String &p_str) { +		_cowdata._ref(p_str._cowdata); +		return *this; +	} +	_FORCE_INLINE_ Char16String(const char16_t *p_cstr) { copy_from(p_cstr); } + +	Char16String &operator=(const char16_t *p_cstr); +	bool operator<(const Char16String &p_right) const; +	Char16String &operator+=(char16_t p_char); +	int length() const { return size() ? size() - 1 : 0; } +	const char16_t *get_data() const; +	operator const char16_t *() const { return get_data(); }; + +protected: +	void copy_from(const char16_t *p_cstr); +}; + +/*************************************************************************/ +/*  CharString                                                           */ +/*************************************************************************/ + +class CharString { +	CowData<char> _cowdata; +	static const char _null; + +public: +	_FORCE_INLINE_ char *ptrw() { return _cowdata.ptrw(); } +	_FORCE_INLINE_ const char *ptr() const { return _cowdata.ptr(); } +	_FORCE_INLINE_ int size() const { return _cowdata.size(); } +	Error resize(int p_size) { return _cowdata.resize(p_size); } + +	_FORCE_INLINE_ char get(int p_index) const { return _cowdata.get(p_index); } +	_FORCE_INLINE_ void set(int p_index, const char &p_elem) { _cowdata.set(p_index, p_elem); } +	_FORCE_INLINE_ const char &operator[](int p_index) const { +		if (unlikely(p_index == _cowdata.size())) { +			return _null; +		} + +		return _cowdata.get(p_index); +	} +	_FORCE_INLINE_ CharProxy<char> operator[](int p_index) { return CharProxy<char>(p_index, _cowdata); } + +	_FORCE_INLINE_ CharString() {} +	_FORCE_INLINE_ CharString(const CharString &p_str) { _cowdata._ref(p_str._cowdata); } +	_FORCE_INLINE_ CharString &operator=(const CharString &p_str) { +		_cowdata._ref(p_str._cowdata); +		return *this; +	} +	_FORCE_INLINE_ CharString(const char *p_cstr) { copy_from(p_cstr); } + +	CharString &operator=(const char *p_cstr); +	bool operator<(const CharString &p_right) const; +	CharString &operator+=(char p_char); +	int length() const { return size() ? size() - 1 : 0; } +	const char *get_data() const; +	operator const char *() const { return get_data(); }; + +protected: +	void copy_from(const char *p_cstr); +}; + +/*************************************************************************/ +/*  String                                                               */ +/*************************************************************************/ + +struct StrRange { +	const char32_t *c_str; +	int len; + +	StrRange(const char32_t *p_c_str = nullptr, int p_len = 0) { +		c_str = p_c_str; +		len = p_len; +	} +}; + +class String { +	CowData<char32_t> _cowdata; +	static const char32_t _null; + +	void copy_from(const char *p_cstr); +	void copy_from(const char *p_cstr, const int p_clip_to); +	void copy_from(const wchar_t *p_cstr); +	void copy_from(const wchar_t *p_cstr, const int p_clip_to); +	void copy_from(const char32_t *p_cstr); +	void copy_from(const char32_t *p_cstr, const int p_clip_to); + +	void copy_from(const char32_t &p_char); + +	void copy_from_unchecked(const char32_t *p_char, const int p_length); + +	bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; +	int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; + +public: +	enum { +		npos = -1 ///<for "some" compatibility with std::string (npos is a huge value in std::string) +	}; + +	_FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); } +	_FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); } + +	void remove(int p_index) { _cowdata.remove(p_index); } + +	_FORCE_INLINE_ void clear() { resize(0); } + +	_FORCE_INLINE_ char32_t get(int p_index) const { return _cowdata.get(p_index); } +	_FORCE_INLINE_ void set(int p_index, const char32_t &p_elem) { _cowdata.set(p_index, p_elem); } +	_FORCE_INLINE_ int size() const { return _cowdata.size(); } +	Error resize(int p_size) { return _cowdata.resize(p_size); } + +	_FORCE_INLINE_ const char32_t &operator[](int p_index) const { +		if (unlikely(p_index == _cowdata.size())) { +			return _null; +		} + +		return _cowdata.get(p_index); +	} +	_FORCE_INLINE_ CharProxy<char32_t> operator[](int p_index) { return CharProxy<char32_t>(p_index, _cowdata); } + +	bool operator==(const String &p_str) const; +	bool operator!=(const String &p_str) const; +	String operator+(const String &p_str) const; + +	String &operator+=(const String &); +	String &operator+=(char32_t p_char); +	String &operator+=(const char *p_str); +	String &operator+=(const wchar_t *p_str); +	String &operator+=(const char32_t *p_str); + +	/* Compatibility Operators */ + +	void operator=(const char *p_str); +	void operator=(const wchar_t *p_str); +	void operator=(const char32_t *p_str); + +	bool operator==(const char *p_str) const; +	bool operator==(const wchar_t *p_str) const; +	bool operator==(const char32_t *p_str) const; +	bool operator==(const StrRange &p_str_range) const; + +	bool operator!=(const char *p_str) const; +	bool operator!=(const wchar_t *p_str) const; +	bool operator!=(const char32_t *p_str) const; + +	bool operator<(const char32_t *p_str) const; +	bool operator<(const char *p_str) const; +	bool operator<(const wchar_t *p_str) const; + +	bool operator<(const String &p_str) const; +	bool operator<=(const String &p_str) const; +	bool operator>(const String &p_str) const; +	bool operator>=(const String &p_str) const; + +	signed char casecmp_to(const String &p_str) const; +	signed char nocasecmp_to(const String &p_str) const; +	signed char naturalnocasecmp_to(const String &p_str) const; + +	const char32_t *get_data() const; +	/* standard size stuff */ + +	_FORCE_INLINE_ int length() const { +		int s = size(); +		return s ? (s - 1) : 0; // length does not include zero +	} + +	bool is_valid_string() const; + +	/* complex helpers */ +	String substr(int p_from, int p_chars = -1) const; +	int find(const String &p_str, int p_from = 0) const; ///< return <0 if failed +	int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed +	int find_char(const char32_t &p_char, int p_from = 0) const; ///< return <0 if failed +	int findn(const String &p_str, int p_from = 0) const; ///< return <0 if failed, case insensitive +	int rfind(const String &p_str, int p_from = -1) const; ///< return <0 if failed +	int rfindn(const String &p_str, int p_from = -1) const; ///< return <0 if failed, case insensitive +	int findmk(const Vector<String> &p_keys, int p_from = 0, int *r_key = nullptr) const; ///< return <0 if failed +	bool match(const String &p_wildcard) const; +	bool matchn(const String &p_wildcard) const; +	bool begins_with(const String &p_string) const; +	bool begins_with(const char *p_string) const; +	bool ends_with(const String &p_string) const; +	bool is_enclosed_in(const String &p_string) const; +	bool is_subsequence_of(const String &p_string) const; +	bool is_subsequence_ofi(const String &p_string) const; +	bool is_quoted() const; +	Vector<String> bigrams() const; +	float similarity(const String &p_string) const; +	String format(const Variant &values, String placeholder = "{_}") const; +	String replace_first(const String &p_key, const String &p_with) const; +	String replace(const String &p_key, const String &p_with) const; +	String replace(const char *p_key, const char *p_with) const; +	String replacen(const String &p_key, const String &p_with) const; +	String repeat(int p_count) const; +	String insert(int p_at_pos, const String &p_string) const; +	String pad_decimals(int p_digits) const; +	String pad_zeros(int p_digits) const; +	String trim_prefix(const String &p_prefix) const; +	String trim_suffix(const String &p_suffix) const; +	String lpad(int min_length, const String &character = " ") const; +	String rpad(int min_length, const String &character = " ") const; +	String sprintf(const Array &values, bool *error) const; +	String quote(String quotechar = "\"") const; +	String unquote() const; +	static String num(double p_num, int p_decimals = -1); +	static String num_scientific(double p_num); +	static String num_real(double p_num); +	static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false); +	static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false); +	static String chr(char32_t p_char); +	static String md5(const uint8_t *p_md5); +	static String hex_encode_buffer(const uint8_t *p_buffer, int p_len); +	bool is_numeric() const; + +	double to_float() const; +	int64_t hex_to_int(bool p_with_prefix = true) const; +	int64_t bin_to_int(bool p_with_prefix = true) const; +	int64_t to_int() const; + +	static int64_t to_int(const char *p_str, int p_len = -1); +	static int64_t to_int(const wchar_t *p_str, int p_len = -1); +	static int64_t to_int(const char32_t *p_str, int p_len = -1, bool p_clamp = false); + +	static double to_float(const char *p_str); +	static double to_float(const wchar_t *p_str, const wchar_t **r_end = nullptr); +	static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr); + +	String capitalize() const; +	String camelcase_to_underscore(bool lowercase = true) const; + +	String get_with_code_lines() const; +	int get_slice_count(String p_splitter) const; +	String get_slice(String p_splitter, int p_slice) const; +	String get_slicec(char32_t p_splitter, int p_slice) const; + +	Vector<String> split(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const; +	Vector<String> rsplit(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const; +	Vector<String> split_spaces() const; +	Vector<float> split_floats(const String &p_splitter, bool p_allow_empty = true) const; +	Vector<float> split_floats_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const; +	Vector<int> split_ints(const String &p_splitter, bool p_allow_empty = true) const; +	Vector<int> split_ints_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const; + +	String join(Vector<String> parts) const; + +	static char32_t char_uppercase(char32_t p_char); +	static char32_t char_lowercase(char32_t p_char); +	String to_upper() const; +	String to_lower() const; + +	int count(const String &p_string, int p_from = 0, int p_to = 0) const; +	int countn(const String &p_string, int p_from = 0, int p_to = 0) const; + +	String left(int p_pos) const; +	String right(int p_pos) const; +	String dedent() const; +	String strip_edges(bool left = true, bool right = true) const; +	String strip_escapes() const; +	String lstrip(const String &p_chars) const; +	String rstrip(const String &p_chars) const; +	String get_extension() const; +	String get_basename() const; +	String plus_file(const String &p_file) const; +	char32_t ord_at(int p_idx) const; + +	void erase(int p_pos, int p_chars); + +	CharString ascii(bool p_allow_extended = false) const; +	CharString utf8() const; +	bool parse_utf8(const char *p_utf8, int p_len = -1); //return true on error +	static String utf8(const char *p_utf8, int p_len = -1); + +	Char16String utf16() const; +	bool parse_utf16(const char16_t *p_utf16, int p_len = -1); //return true on error +	static String utf16(const char16_t *p_utf16, int p_len = -1); + +	static uint32_t hash(const char32_t *p_cstr, int p_len); /* hash the string */ +	static uint32_t hash(const char32_t *p_cstr); /* hash the string */ +	static uint32_t hash(const wchar_t *p_cstr, int p_len); /* hash the string */ +	static uint32_t hash(const wchar_t *p_cstr); /* hash the string */ +	static uint32_t hash(const char *p_cstr, int p_len); /* hash the string */ +	static uint32_t hash(const char *p_cstr); /* hash the string */ +	uint32_t hash() const; /* hash the string */ +	uint64_t hash64() const; /* hash the string */ +	String md5_text() const; +	String sha1_text() const; +	String sha256_text() const; +	Vector<uint8_t> md5_buffer() const; +	Vector<uint8_t> sha1_buffer() const; +	Vector<uint8_t> sha256_buffer() const; + +	_FORCE_INLINE_ bool empty() const { return length() == 0; } + +	// path functions +	bool is_abs_path() const; +	bool is_rel_path() const; +	bool is_resource_file() const; +	String path_to(const String &p_path) const; +	String path_to_file(const String &p_path) const; +	String get_base_dir() const; +	String get_file() const; +	static String humanize_size(uint64_t p_size); +	String simplify_path() const; + +	String xml_escape(bool p_escape_quotes = false) const; +	String xml_unescape() const; +	String http_escape() const; +	String http_unescape() const; +	String c_escape() const; +	String c_escape_multiline() const; +	String c_unescape() const; +	String json_escape() const; +	String word_wrap(int p_chars_per_line) const; + +	String percent_encode() const; +	String percent_decode() const; + +	String property_name_encode() const; + +	bool is_valid_identifier() const; +	bool is_valid_integer() const; +	bool is_valid_float() const; +	bool is_valid_hex_number(bool p_with_prefix) const; +	bool is_valid_html_color() const; +	bool is_valid_ip_address() const; +	bool is_valid_filename() const; + +	/** +	 * The constructors must not depend on other overloads +	 */ + +	_FORCE_INLINE_ String() {} +	_FORCE_INLINE_ String(const String &p_str) { _cowdata._ref(p_str._cowdata); } + +	String &operator=(const String &p_str) { +		_cowdata._ref(p_str._cowdata); +		return *this; +	} + +	Vector<uint8_t> to_ascii_buffer() const; +	Vector<uint8_t> to_utf8_buffer() const; +	Vector<uint8_t> to_utf16_buffer() const; +	Vector<uint8_t> to_utf32_buffer() const; + +	String(const char *p_str); +	String(const wchar_t *p_str); +	String(const char32_t *p_str); +	String(const char *p_str, int p_clip_to_len); +	String(const wchar_t *p_str, int p_clip_to_len); +	String(const char32_t *p_str, int p_clip_to_len); +	String(const StrRange &p_range); +}; + +bool operator==(const char *p_chr, const String &p_str); +bool operator==(const wchar_t *p_chr, const String &p_str); +bool operator!=(const char *p_chr, const String &p_str); +bool operator!=(const wchar_t *p_chr, const String &p_str); + +String operator+(const char *p_chr, const String &p_str); +String operator+(const wchar_t *p_chr, const String &p_str); +String operator+(char32_t p_chr, const String &p_str); + +String itos(int64_t p_val); +String uitos(uint64_t p_val); +String rtos(double p_val); +String rtoss(double p_val); //scientific version + +struct NoCaseComparator { +	bool operator()(const String &p_a, const String &p_b) const { +		return p_a.nocasecmp_to(p_b) < 0; +	} +}; + +struct NaturalNoCaseComparator { +	bool operator()(const String &p_a, const String &p_b) const { +		return p_a.naturalnocasecmp_to(p_b) < 0; +	} +}; + +template <typename L, typename R> +_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) { +	while (true) { +		const char32_t l = *l_ptr; +		const char32_t r = *r_ptr; + +		if (l == 0 && r == 0) { +			return false; +		} else if (l == 0) { +			return true; +		} else if (r == 0) { +			return false; +		} else if (l < r) { +			return true; +		} else if (l > r) { +			return false; +		} + +		l_ptr++; +		r_ptr++; +	} +} + +/* end of namespace */ + +// Tool translate (TTR and variants) for the editor UI, +// and doc translate for the class reference (DTR). +#ifdef TOOLS_ENABLED +// Gets parsed. +String TTR(const String &p_text, const String &p_context = ""); +String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = ""); +String DTR(const String &p_text, const String &p_context = ""); +String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = ""); +// Use for C strings. +#define TTRC(m_value) (m_value) +// Use to avoid parsing (for use later with C strings). +#define TTRGET(m_value) TTR(m_value) + +#else +#define TTR(m_value) (String()) +#define TTRN(m_value) (String()) +#define DTR(m_value) (String()) +#define DTRN(m_value) (String()) +#define TTRC(m_value) (m_value) +#define TTRGET(m_value) (m_value) +#endif + +// Runtime translate for the public node API. +String RTR(const String &p_text, const String &p_context = ""); +String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = ""); + +bool is_symbol(char32_t c); +bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end); + +_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr) { +} + +_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr, const String &p_str) { +	arr.push_back(p_str); +} + +template <class... P> +_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr, const String &p_str, P... p_args) { +	arr.push_back(p_str); +	sarray_add_str(arr, p_args...); +} + +template <class... P> +_FORCE_INLINE_ Vector<String> sarray(P... p_args) { +	Vector<String> arr; +	sarray_add_str(arr, p_args...); +	return arr; +} + +#endif // USTRING_H  |