summaryrefslogtreecommitdiff
path: root/editor/doc_tools.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/doc_tools.cpp')
-rw-r--r--editor/doc_tools.cpp244
1 files changed, 177 insertions, 67 deletions
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index beead74c53..6acf654b04 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 */
@@ -37,11 +37,41 @@
#include "core/io/dir_access.h"
#include "core/io/marshalls.h"
#include "core/object/script_language.h"
+#include "core/string/translation.h"
#include "core/version.h"
#include "scene/resources/theme.h"
// Used for a hack preserving Mono properties on non-Mono builds.
-#include "modules/modules_enabled.gen.h"
+#include "modules/modules_enabled.gen.h" // For mono.
+
+static String _get_indent(const String &p_text) {
+ String indent;
+ bool has_text = false;
+ int line_start = 0;
+
+ for (int i = 0; i < p_text.length(); i++) {
+ const char32_t c = p_text[i];
+ if (c == '\n') {
+ line_start = i + 1;
+ } else if (c > 32) {
+ has_text = true;
+ indent = p_text.substr(line_start, i - line_start);
+ break; // Indentation of the first line that has text.
+ }
+ }
+ if (!has_text) {
+ return p_text;
+ }
+ return indent;
+}
+
+static String _translate_doc_string(const String &p_text) {
+ const String indent = _get_indent(p_text);
+ const String message = p_text.dedent().strip_edges();
+ const String translated = TranslationServer::get_singleton()->doc_translate(message, "");
+ // No need to restore stripped edges because they'll be stripped again later.
+ return translated.indent(indent);
+}
void DocTools::merge_from(const DocTools &p_data) {
for (KeyValue<String, DocData::ClassDoc> &E : class_list) {
@@ -57,25 +87,21 @@ void DocTools::merge_from(const DocTools &p_data) {
c.brief_description = cf.brief_description;
c.tutorials = cf.tutorials;
- for (int i = 0; i < c.methods.size(); i++) {
- DocData::MethodDoc &m = c.methods.write[i];
+ for (int i = 0; i < c.constructors.size(); i++) {
+ DocData::MethodDoc &m = c.constructors.write[i];
- for (int j = 0; j < cf.methods.size(); j++) {
- if (cf.methods[j].name != m.name) {
+ for (int j = 0; j < cf.constructors.size(); j++) {
+ if (cf.constructors[j].name != m.name) {
continue;
}
- const char *operator_prefix = "operator "; // Operators use a space at the end, making this prefix an invalid identifier (and differentiating from methods).
-
- if (cf.methods[j].name == c.name || cf.methods[j].name.begins_with(operator_prefix)) {
- // Since constructors and operators can repeat, we need to check the type of
+ {
+ // Since constructors can repeat, we need to check the type of
// the arguments so we make sure they are different.
-
- if (cf.methods[j].arguments.size() != m.arguments.size()) {
+ if (cf.constructors[j].arguments.size() != m.arguments.size()) {
continue;
}
-
- int arg_count = cf.methods[j].arguments.size();
+ int arg_count = cf.constructors[j].arguments.size();
Vector<bool> arg_used;
arg_used.resize(arg_count);
for (int l = 0; l < arg_count; ++l) {
@@ -85,7 +111,7 @@ void DocTools::merge_from(const DocTools &p_data) {
// have to check one by one so we make sure we have an exact match
for (int k = 0; k < arg_count; ++k) {
for (int l = 0; l < arg_count; ++l) {
- if (cf.methods[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) {
+ if (cf.constructors[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) {
arg_used.write[l] = true;
break;
}
@@ -102,6 +128,21 @@ void DocTools::merge_from(const DocTools &p_data) {
}
}
+ const DocData::MethodDoc &mf = cf.constructors[j];
+
+ m.description = mf.description;
+ break;
+ }
+ }
+
+ for (int i = 0; i < c.methods.size(); i++) {
+ DocData::MethodDoc &m = c.methods.write[i];
+
+ for (int j = 0; j < cf.methods.size(); j++) {
+ if (cf.methods[j].name != m.name) {
+ continue;
+ }
+
const DocData::MethodDoc &mf = cf.methods[j];
m.description = mf.description;
@@ -165,6 +206,54 @@ void DocTools::merge_from(const DocTools &p_data) {
}
}
+ for (int i = 0; i < c.operators.size(); i++) {
+ DocData::MethodDoc &m = c.operators.write[i];
+
+ for (int j = 0; j < cf.operators.size(); j++) {
+ if (cf.operators[j].name != m.name) {
+ continue;
+ }
+
+ {
+ // Since operators can repeat, we need to check the type of
+ // the arguments so we make sure they are different.
+ if (cf.operators[j].arguments.size() != m.arguments.size()) {
+ continue;
+ }
+ int arg_count = cf.operators[j].arguments.size();
+ Vector<bool> arg_used;
+ arg_used.resize(arg_count);
+ for (int l = 0; l < arg_count; ++l) {
+ arg_used.write[l] = false;
+ }
+ // also there is no guarantee that argument ordering will match, so we
+ // have to check one by one so we make sure we have an exact match
+ for (int k = 0; k < arg_count; ++k) {
+ for (int l = 0; l < arg_count; ++l) {
+ if (cf.operators[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) {
+ arg_used.write[l] = true;
+ break;
+ }
+ }
+ }
+ bool not_the_same = false;
+ for (int l = 0; l < arg_count; ++l) {
+ if (!arg_used[l]) { // at least one of the arguments was different
+ not_the_same = true;
+ }
+ }
+ if (not_the_same) {
+ continue;
+ }
+ }
+
+ const DocData::MethodDoc &mf = cf.operators[j];
+
+ m.description = mf.description;
+ break;
+ }
+ }
+
#ifndef MODULE_MONO_ENABLED
// The Mono module defines some properties that we want to keep when
// re-generating docs with a non-Mono build, to prevent pointless diffs
@@ -193,17 +282,17 @@ void DocTools::remove_from(const DocTools &p_data) {
}
void DocTools::add_doc(const DocData::ClassDoc &p_class_doc) {
- ERR_FAIL_COND(p_class_doc.name == "");
+ ERR_FAIL_COND(p_class_doc.name.is_empty());
class_list[p_class_doc.name] = p_class_doc;
}
void DocTools::remove_doc(const String &p_class_name) {
- ERR_FAIL_COND(p_class_name == "" || !class_list.has(p_class_name));
+ ERR_FAIL_COND(p_class_name.is_empty() || !class_list.has(p_class_name));
class_list.erase(p_class_name);
}
bool DocTools::has_doc(const String &p_class_name) {
- if (p_class_name == "") {
+ if (p_class_name.is_empty()) {
return false;
}
return class_list.has(p_class_name);
@@ -261,7 +350,7 @@ void DocTools::generate(bool p_basic_types) {
List<PropertyInfo> properties;
List<PropertyInfo> own_properties;
if (name == "ProjectSettings") {
- //special case for project settings, so settings can be documented
+ // Special case for project settings, so settings can be documented.
ProjectSettings::get_singleton()->get_property_list(&properties);
own_properties = properties;
} else {
@@ -269,9 +358,12 @@ void DocTools::generate(bool p_basic_types) {
ClassDB::get_property_list(name, &own_properties, true);
}
+ properties.sort();
+ own_properties.sort();
+
List<PropertyInfo>::Element *EO = own_properties.front();
for (const PropertyInfo &E : properties) {
- bool inherited = EO == nullptr;
+ bool inherited = true;
if (EO && EO->get() == E) {
inherited = false;
EO = EO->next();
@@ -282,11 +374,17 @@ void DocTools::generate(bool p_basic_types) {
}
DocData::PropertyDoc prop;
-
prop.name = E.name;
-
prop.overridden = inherited;
+ if (inherited) {
+ String parent = ClassDB::get_parent_class(c.name);
+ while (!ClassDB::has_property(parent, prop.name, true)) {
+ parent = ClassDB::get_parent_class(parent);
+ }
+ prop.overrides = parent;
+ }
+
bool default_value_valid = false;
Variant default_value;
@@ -315,7 +413,7 @@ void DocTools::generate(bool p_basic_types) {
//used to track uninitialized values using valgrind
//print_line("getting default value for " + String(name) + "." + String(E.name));
if (default_value_valid && default_value.get_type() != Variant::OBJECT) {
- prop.default_value = default_value.get_construct_string().replace("\n", "");
+ prop.default_value = default_value.get_construct_string().replace("\n", " ");
}
StringName setter = ClassDB::get_property_setter(name, E.name);
@@ -372,7 +470,7 @@ void DocTools::generate(bool p_basic_types) {
method_list.sort();
for (const MethodInfo &E : method_list) {
- if (E.name == "" || (E.name[0] == '_' && !(E.flags & METHOD_FLAG_VIRTUAL))) {
+ if (E.name.is_empty() || (E.name[0] == '_' && !(E.flags & METHOD_FLAG_VIRTUAL))) {
continue; //hidden, don't count
}
@@ -394,21 +492,21 @@ void DocTools::generate(bool p_basic_types) {
}
if (E.flags & METHOD_FLAG_CONST) {
- if (method.qualifiers != "") {
+ if (!method.qualifiers.is_empty()) {
method.qualifiers += " ";
}
method.qualifiers += "const";
}
if (E.flags & METHOD_FLAG_VARARG) {
- if (method.qualifiers != "") {
+ if (!method.qualifiers.is_empty()) {
method.qualifiers += " ";
}
method.qualifiers += "vararg";
}
if (E.flags & METHOD_FLAG_STATIC) {
- if (method.qualifiers != "") {
+ if (!method.qualifiers.is_empty()) {
method.qualifiers += " ";
}
method.qualifiers += "static";
@@ -427,7 +525,7 @@ void DocTools::generate(bool p_basic_types) {
int darg_idx = i - (E.arguments.size() - E.default_arguments.size());
if (darg_idx >= 0) {
Variant default_arg = E.default_arguments[darg_idx];
- argument.default_value = default_arg.get_construct_string();
+ argument.default_value = default_arg.get_construct_string().replace("\n", " ");
}
method.arguments.push_back(argument);
@@ -490,7 +588,7 @@ void DocTools::generate(bool p_basic_types) {
tid.name = E;
tid.type = "Color";
tid.data_type = "color";
- tid.default_value = Variant(Theme::get_default()->get_color(E, cname)).get_construct_string();
+ tid.default_value = Variant(Theme::get_default()->get_color(E, cname)).get_construct_string().replace("\n", " ");
c.theme_properties.push_back(tid);
}
@@ -544,6 +642,8 @@ void DocTools::generate(bool p_basic_types) {
tid.data_type = "style";
c.theme_properties.push_back(tid);
}
+
+ c.theme_properties.sort();
}
classes.pop_front();
@@ -650,11 +750,6 @@ void DocTools::generate(bool p_basic_types) {
DocData::MethodDoc method;
method.name = mi.name;
- if (method.name == cname) {
- method.qualifiers = "constructor";
- } else if (method.name.begins_with("operator")) {
- method.qualifiers = "operator";
- }
for (int j = 0; j < mi.arguments.size(); j++) {
PropertyInfo arginfo = mi.arguments[j];
@@ -665,7 +760,7 @@ void DocTools::generate(bool p_basic_types) {
int darg_idx = mi.default_arguments.size() - mi.arguments.size() + j;
if (darg_idx >= 0) {
Variant default_arg = mi.default_arguments[darg_idx];
- ad.default_value = default_arg.get_construct_string();
+ ad.default_value = default_arg.get_construct_string().replace("\n", " ");
}
method.arguments.push_back(ad);
@@ -674,27 +769,33 @@ void DocTools::generate(bool p_basic_types) {
DocData::return_doc_from_retinfo(method, mi.return_val);
if (mi.flags & METHOD_FLAG_VARARG) {
- if (method.qualifiers != "") {
+ if (!method.qualifiers.is_empty()) {
method.qualifiers += " ";
}
method.qualifiers += "vararg";
}
if (mi.flags & METHOD_FLAG_CONST) {
- if (method.qualifiers != "") {
+ if (!method.qualifiers.is_empty()) {
method.qualifiers += " ";
}
method.qualifiers += "const";
}
if (mi.flags & METHOD_FLAG_STATIC) {
- if (method.qualifiers != "") {
+ if (!method.qualifiers.is_empty()) {
method.qualifiers += " ";
}
method.qualifiers += "static";
}
- c.methods.push_back(method);
+ if (method.name == cname) {
+ c.constructors.push_back(method);
+ } else if (method.name.begins_with("operator")) {
+ c.operators.push_back(method);
+ } else {
+ c.methods.push_back(method);
+ }
}
List<PropertyInfo> properties;
@@ -703,7 +804,7 @@ void DocTools::generate(bool p_basic_types) {
DocData::PropertyDoc property;
property.name = pi.name;
property.type = Variant::get_type_name(pi.type);
- property.default_value = v.get(pi.name).get_construct_string();
+ property.default_value = v.get(pi.name).get_construct_string().replace("\n", " ");
c.properties.push_back(property);
}
@@ -715,7 +816,7 @@ void DocTools::generate(bool p_basic_types) {
DocData::ConstantDoc constant;
constant.name = E;
Variant value = Variant::get_constant_value(Variant::Type(i), E);
- constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string();
+ constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace("\n", " ");
constant.is_value_valid = true;
c.constants.push_back(constant);
}
@@ -817,7 +918,7 @@ void DocTools::generate(bool p_basic_types) {
md.name = mi.name;
if (mi.flags & METHOD_FLAG_VARARG) {
- if (md.qualifiers != "") {
+ if (!md.qualifiers.is_empty()) {
md.qualifiers += " ";
}
md.qualifiers += "vararg";
@@ -832,7 +933,7 @@ void DocTools::generate(bool p_basic_types) {
int darg_idx = j - (mi.arguments.size() - mi.default_arguments.size());
if (darg_idx >= 0) {
Variant default_arg = mi.default_arguments[darg_idx];
- ad.default_value = default_arg.get_construct_string();
+ ad.default_value = default_arg.get_construct_string().replace("\n", " ");
}
md.arguments.push_back(ad);
@@ -916,7 +1017,7 @@ static Error _parse_methods(Ref<XMLParser> &parser, Vector<DocData::MethodDoc> &
methods.push_back(method);
} else {
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + parser->get_node_name() + ".");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + parser->get_node_name() + ", expected " + element + ".");
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == section) {
@@ -937,7 +1038,7 @@ Error DocTools::load_classes(const String &p_dir) {
da->list_dir_begin();
String path;
path = da->get_next();
- while (path != String()) {
+ while (!path.is_empty()) {
if (!da->current_is_dir() && path.ends_with("xml")) {
Ref<XMLParser> parser = memnew(XMLParser);
Error err2 = parser->open(p_dir.plus_file(path));
@@ -967,7 +1068,7 @@ Error DocTools::erase_classes(const String &p_dir) {
da->list_dir_begin();
String path;
path = da->get_next();
- while (path != String()) {
+ while (!path.is_empty()) {
if (!da->current_is_dir() && path.ends_with("xml")) {
to_erase.push_back(path);
}
@@ -1044,10 +1145,15 @@ Error DocTools::_load(Ref<XMLParser> parser) {
break; // End of <tutorials>.
}
}
+ } else if (name2 == "constructors") {
+ Error err2 = _parse_methods(parser, c.constructors);
+ ERR_FAIL_COND_V(err2, err2);
} else if (name2 == "methods") {
Error err2 = _parse_methods(parser, c.methods);
ERR_FAIL_COND_V(err2, err2);
-
+ } else if (name2 == "operators") {
+ Error err2 = _parse_methods(parser, c.operators);
+ ERR_FAIL_COND_V(err2, err2);
} else if (name2 == "signals") {
Error err2 = _parse_methods(parser, c.signals);
ERR_FAIL_COND_V(err2, err2);
@@ -1163,7 +1269,7 @@ Error DocTools::_load(Ref<XMLParser> parser) {
}
static void _write_string(FileAccess *f, int p_tablevel, const String &p_string) {
- if (p_string == "") {
+ if (p_string.is_empty()) {
return;
}
String tab;
@@ -1181,15 +1287,15 @@ static void _write_method_doc(FileAccess *f, const String &p_name, Vector<DocDat
const DocData::MethodDoc &m = p_method_docs[i];
String qualifiers;
- if (m.qualifiers != "") {
+ if (!m.qualifiers.is_empty()) {
qualifiers += " qualifiers=\"" + m.qualifiers.xml_escape() + "\"";
}
_write_string(f, 2, "<" + p_name + " name=\"" + m.name.xml_escape() + "\"" + qualifiers + ">");
- if (m.return_type != "") {
+ if (!m.return_type.is_empty()) {
String enum_text;
- if (m.return_enum != String()) {
+ if (!m.return_enum.is_empty()) {
enum_text = " enum=\"" + m.return_enum + "\"";
}
_write_string(f, 3, "<return type=\"" + m.return_type + "\"" + enum_text + " />");
@@ -1204,11 +1310,11 @@ static void _write_method_doc(FileAccess *f, const String &p_name, Vector<DocDat
const DocData::ArgumentDoc &a = m.arguments[j];
String enum_text;
- if (a.enumeration != String()) {
+ if (!a.enumeration.is_empty()) {
enum_text = " enum=\"" + a.enumeration + "\"";
}
- if (a.default_value != "") {
+ if (!a.default_value.is_empty()) {
_write_string(f, 3, "<argument index=\"" + itos(j) + "\" name=\"" + a.name.xml_escape() + "\" type=\"" + a.type.xml_escape() + "\"" + enum_text + " default=\"" + a.default_value.xml_escape(true) + "\" />");
} else {
_write_string(f, 3, "<argument index=\"" + itos(j) + "\" name=\"" + a.name.xml_escape() + "\" type=\"" + a.type.xml_escape() + "\"" + enum_text + " />");
@@ -1216,7 +1322,7 @@ static void _write_method_doc(FileAccess *f, const String &p_name, Vector<DocDat
}
_write_string(f, 3, "<description>");
- _write_string(f, 4, m.description.strip_edges().xml_escape());
+ _write_string(f, 4, _translate_doc_string(m.description).strip_edges().xml_escape());
_write_string(f, 3, "</description>");
_write_string(f, 2, "</" + p_name + ">");
@@ -1246,7 +1352,7 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
_write_string(f, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
String header = "<class name=\"" + c.name + "\"";
- if (c.inherits != "") {
+ if (!c.inherits.is_empty()) {
header += " inherits=\"" + c.inherits + "\"";
}
header += String(" version=\"") + VERSION_BRANCH + "\"";
@@ -1254,11 +1360,11 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
_write_string(f, 0, header);
_write_string(f, 1, "<brief_description>");
- _write_string(f, 2, c.brief_description.strip_edges().xml_escape());
+ _write_string(f, 2, _translate_doc_string(c.brief_description).strip_edges().xml_escape());
_write_string(f, 1, "</brief_description>");
_write_string(f, 1, "<description>");
- _write_string(f, 2, c.description.strip_edges().xml_escape());
+ _write_string(f, 2, _translate_doc_string(c.description).strip_edges().xml_escape());
_write_string(f, 1, "</description>");
_write_string(f, 1, "<tutorials>");
@@ -1269,6 +1375,8 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
}
_write_string(f, 1, "</tutorials>");
+ _write_method_doc(f, "constructor", c.constructors);
+
_write_method_doc(f, "method", c.methods);
if (!c.properties.is_empty()) {
@@ -1278,20 +1386,20 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
for (int i = 0; i < c.properties.size(); i++) {
String additional_attributes;
- if (c.properties[i].enumeration != String()) {
+ if (!c.properties[i].enumeration.is_empty()) {
additional_attributes += " enum=\"" + c.properties[i].enumeration + "\"";
}
- if (c.properties[i].default_value != String()) {
+ if (!c.properties[i].default_value.is_empty()) {
additional_attributes += " default=\"" + c.properties[i].default_value.xml_escape(true) + "\"";
}
const DocData::PropertyDoc &p = c.properties[i];
if (c.properties[i].overridden) {
- _write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\" override=\"true\"" + additional_attributes + " />");
+ _write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\" overrides=\"" + p.overrides + "\"" + additional_attributes + " />");
} else {
_write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\"" + additional_attributes + ">");
- _write_string(f, 3, p.description.strip_edges().xml_escape());
+ _write_string(f, 3, _translate_doc_string(p.description).strip_edges().xml_escape());
_write_string(f, 2, "</member>");
}
}
@@ -1305,19 +1413,19 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
for (int i = 0; i < c.constants.size(); i++) {
const DocData::ConstantDoc &k = c.constants[i];
if (k.is_value_valid) {
- if (k.enumeration != String()) {
+ if (!k.enumeration.is_empty()) {
_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">");
} else {
_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\">");
}
} else {
- if (k.enumeration != String()) {
+ if (!k.enumeration.is_empty()) {
_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\" enum=\"" + k.enumeration + "\">");
} else {
_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\">");
}
}
- _write_string(f, 3, k.description.strip_edges().xml_escape());
+ _write_string(f, 3, _translate_doc_string(k.description).strip_edges().xml_escape());
_write_string(f, 2, "</constant>");
}
@@ -1331,19 +1439,21 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
for (int i = 0; i < c.theme_properties.size(); i++) {
const DocData::ThemeItemDoc &ti = c.theme_properties[i];
- if (ti.default_value != "") {
+ if (!ti.default_value.is_empty()) {
_write_string(f, 2, "<theme_item name=\"" + ti.name + "\" data_type=\"" + ti.data_type + "\" type=\"" + ti.type + "\" default=\"" + ti.default_value.xml_escape(true) + "\">");
} else {
_write_string(f, 2, "<theme_item name=\"" + ti.name + "\" data_type=\"" + ti.data_type + "\" type=\"" + ti.type + "\">");
}
- _write_string(f, 3, ti.description.strip_edges().xml_escape());
+ _write_string(f, 3, _translate_doc_string(ti.description).strip_edges().xml_escape());
_write_string(f, 2, "</theme_item>");
}
_write_string(f, 1, "</theme_items>");
}
+ _write_method_doc(f, "operator", c.operators);
+
_write_string(f, 0, "</class>");
}