FlightGear next
DefaultTranslationParser.cxx
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 Florent Rougon
2// SPDX-License-Identifier: GPL-2.0-or-later
3
8
10
11#include <cassert>
12#include <string>
13#include <utility>
14#include <cstring>
15
16#include <simgear/structure/exception.hxx>
17#include <simgear/debug/logstream.hxx>
18
19using std::string;
20
21namespace flightgear
22{
23
27
30
33
34bool DefaultTranslationParser::asBoolean(const string& str)
35{
36 if (str == "true") {
37 return true;
38 } else if (str == "false") {
39 return false;
40 }
41
42 const string message = ("invalid boolean value '" + str +
43 "' (expected 'true' or 'false')");
44 const sg_location location(getPath(), getLine(), getColumn());
45 throw sg_io_exception(message, location, SG_ORIGIN, false);
46}
47
48[[noreturn]] void DefaultTranslationParser::parseError(const string& message)
49{
50 const sg_location location(getPath(), getLine(), getColumn());
51 throw sg_io_exception(message, location, SG_ORIGIN, false);
52}
53
55 const XMLAttributes& attrs)
56{
57 bool allowedElement;
58 const char* hasPluralStr = attrs.getValue("has-plural");
59
60 switch (_state) {
61 case State::LOOKING_FOR_RESOURCE_ELEMENT:
62 if (!std::strcmp(name, "resource")) {
63 _state = State::LOOKING_FOR_META_ELEMENT;
64 } else {
65 parseError("Expected the root element to be 'resource', "
66 "but found '" + string(name) + "' instead");
67 }
68 break;
69 case State::LOOKING_FOR_META_ELEMENT:
70 if (!std::strcmp(name, "meta")) {
71 _state = State::READING_META_ELEMENT;
72 } else {
73 parseError("Expected a 'meta' element here, but found '"
74 + string(name) + "' instead");
75 }
76 break;
77 case State::READING_META_ELEMENT:
78 allowedElement = false;
79
80 for (const string eltName : {"file-type", "format-version", "description",
81 "language-description"}) {
82 if (name == eltName) {
83 startElementInsideMeta(eltName);
84 allowedElement = true;
85 break;
86 }
87 }
88
89 if (!allowedElement) {
90 parseError("Unexpected element '" + string(name) +
91 "' inside 'meta' element");
92 }
93 break;
94 case State::LOOKING_FOR_STRINGS_ELEMENT:
95 if (!std::strcmp(name, "strings")) {
96 _state = State::READING_STRINGS_ELEMENT;
97 } else {
98 parseError("Expected a 'strings' element after 'meta', but found '"
99 + string(name) + "'");
100 }
101 break;
102 case State::READING_STRINGS_ELEMENT:
103 _stringTagName = name;
104 _hasPlural = hasPluralStr && asBoolean(hasPluralStr);
105 _text.clear(); // we'll gather the element's contents
106 _state = State::READING_TRANSLATABLE_STRING;
107 break;
108 case State::READING_FILE_TYPE_ELEMENT:
109 parseError("Unexpected element '" + string(name) +
110 "' inside <file-type>");
111 case State::READING_FORMAT_VERSION_ELEMENT:
112 parseError("Unexpected element '" + string(name) +
113 "' inside <format-version>");
114 case State::READING_TRANSLATABLE_STRING:
115 parseError("Unexpected element '" + string(name) +
116 "' inside translatable string '" + _stringTagName + "'");
117 case State::AFTER_STRINGS_ELEMENT:
118 parseError("Unexpected element '" + string(name) +
119 "' after the 'strings' element");
120 }
121}
122
123void DefaultTranslationParser::startElementInsideMeta(const std::string& name)
124{
125 if (name == "file-type") {
126 if (_foundFileType) {
127 parseError("Only one 'file-type' element is allowed inside 'meta'.");
128 }
129
130 _foundFileType = true;
131 _state = State::READING_FILE_TYPE_ELEMENT;
132 } else if (name == "format-version") {
133 if (_foundFormatVersion) {
134 parseError("Only one 'format-version' element is allowed inside "
135 "'meta'.");
136 }
137
138 _foundFormatVersion = true;
139 _state = State::READING_FORMAT_VERSION_ELEMENT;
140 } // We ignore other legal elements here for now.
141
142 _text.clear(); // we'll gather the element's contents
143}
144
146{
147 const string expectedFileType = "FlightGear default translation file";
148 const string expectedFormatVersion = "1";
149
150 switch (_state) {
151 case State::LOOKING_FOR_RESOURCE_ELEMENT:
152 assert(false);
153 break;
154 case State::LOOKING_FOR_META_ELEMENT:
155 parseError("Expected a 'meta' element as the first child of 'resource'");
156 case State::READING_FILE_TYPE_ELEMENT:
157 _fileType = std::move(_text);
158
159 if (_fileType != expectedFileType) {
160 parseError("Expected body of 'file-type' element to be '" +
161 expectedFileType + "', not '" + _fileType + "'");
162 }
163
164 _state = State::READING_META_ELEMENT;
165 break;
166 case State::READING_FORMAT_VERSION_ELEMENT:
167 _formatVersion = std::move(_text);
168
169 if (_formatVersion != expectedFormatVersion) {
170 parseError("Expected body of 'format-version' element to be '" +
171 expectedFormatVersion + "', not '" + _formatVersion +
172 "'");
173 }
174
175 _state = State::READING_META_ELEMENT;
176 break;
177 case State::READING_META_ELEMENT:
178 if (!std::strcmp(name, "meta")) {
179 checkIfFormatIsSupported(); // 'meta' element now finished, go!
180 _state = State::LOOKING_FOR_STRINGS_ELEMENT;
181 } // else it should be an end tag for other supported <meta> children,
182 // namely 'description' or 'language-description' for now.
183 break;
184 case State::LOOKING_FOR_STRINGS_ELEMENT:
185 // No <strings> element: the file has no translatable strings, no big
186 // deal
187 break;
188 case State::READING_TRANSLATABLE_STRING:
189 _resource->addTranslationUnit(std::move(_stringTagName),
190 _nextIndex[_stringTagName]++,
191 std::move(_text), _hasPlural);
192 _state = State::READING_STRINGS_ELEMENT;
193 break;
194 case State::READING_STRINGS_ELEMENT:
195 _state = State::AFTER_STRINGS_ELEMENT;
196 break;
197 case State::AFTER_STRINGS_ELEMENT:
198 assert(!std::strcmp(name, "resource"));
199 }
200}
201
202void DefaultTranslationParser::data(const char * s, int len)
203{
204 _text += string(s, len);
205}
206
207void DefaultTranslationParser::warning(const char* message, int line,
208 int column)
209{
210 SG_LOG(SG_GENERAL, SG_WARN, "Warning: " << message << " (line " << line
211 << ", column " << column << ')');
212}
213
214void DefaultTranslationParser::checkIfFormatIsSupported()
215{
216 if (!_foundFileType) {
217 parseError("'file-type' element is required inside the 'meta' element");
218 } else if (!_foundFormatVersion) {
219 parseError("'format-version' element is required inside the 'meta' "
220 "element");
221 }
222
223 // We've already checked the values of _fileType and _formatVersion as
224 // soon as we found them (otherwise, in case of a problem, the line and
225 // column info would be inaccurate). So, we're all good!
226
227 return;
228}
229
230} // namespace flightgear
Parse a FlightGear default translation file (e.g., menu.xml)
Class that holds translation units within a resource (“context”)
void data(const char *s, int len) override
void startElement(const char *name, const XMLAttributes &atts) override
DefaultTranslationParser(TranslationResource *resource)
void warning(const char *message, int line, int column) override
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
const char * name