FlightGear next
XLIFFParser.cxx
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2018 James Turner <james@flightgear.org>
2// SPDX-License-Identifier: GPL-2.0-or-later
3
8
9#include "config.h"
10
11#include "XLIFFParser.hxx"
12
13#include <cassert>
14#include <cstring>
15#include <regex>
16#include <string>
17#include <tuple>
18#include <utility>
19
20// simgear
21#include <simgear/debug/logstream.hxx>
22#include <simgear/props/props.hxx>
23#include <simgear/misc/strutils.hxx>
24
25#include <GUI/MessageBox.hxx>
26#include "LanguageInfo.hxx"
27
28using namespace flightgear;
29namespace strutils = simgear::strutils;
30
31XLIFFParser::XLIFFParser(const std::string& languageId,
32 TranslationDomain* domain) :
33 _languageId(languageId), _domain(domain)
34{
35
36}
37
39{
40
41}
42
44{
45
46}
47
48void XLIFFParser::startElement(const char *name, const XMLAttributes &atts)
49{
50 _text.clear();
51 std::string tag(name);
52
53 if (_skipElements) {
54 return;
55 } else if (tag == "file") {
56 const char* origName_c = atts.getValue("original");
57
58 if (origName_c && !std::strcmp(origName_c, "Obsolete_PO_entries")) {
59 _skipElements = true; // skip all the contents of this <file> element
60 }
61 } else if (tag == "trans-unit") {
62 startTransUnitElement(atts);
63 } else if (tag == "group") {
64 const char* resType_c = atts.getValue("restype");
65
66 if (resType_c && !std::strcmp(resType_c,
67 "x-trolltech-linguist-context")) {
68 startContextGroup(atts.getValue("resname"));
69 } else if (resType_c && !std::strcmp(resType_c, "x-gettext-plurals")) {
70 startPluralGroup(atts.getValue("id"));
71 }
72 }
73}
74
75void XLIFFParser::startTransUnitElement(const XMLAttributes &atts)
76{
77 const char* id_c = atts.getValue("id");
78 if (!id_c) {
80 "Error while parsing a .xlf file",
81 "<trans-unit> element with no 'id' attribute.",
82 "Illegal <trans-unit> element with no 'id' attribute at " +
83 std::to_string(getLine()) + " of " + getPath() + ".");
84 }
85
86 if (!_pluralGroupId.empty()) { // if inside a plural group
87 checkIdOfPluralTransUnit(id_c);
88 _expectedPluralFormIndex++;
89 } else { // non-plural translation unit
90 std::tie(_resource, _basicId, _index) = parseSimpleTransUnitId(id_c);
91 }
92}
93
94void XLIFFParser::checkIdOfPluralTransUnit(std::string transUnitId)
95{
96 const std::string expectedId =
97 _pluralGroupId + "[" + std::to_string(_expectedPluralFormIndex) + "]";
98
99 if (transUnitId != expectedId) {
101 "Error while parsing a .xlf file",
102 "Unexpected value '" + transUnitId + "' for 'id' attribute of "
103 "<trans-unit> element found inside plural group with id='" +
104 _pluralGroupId + "' (expected: '" + expectedId + "') at " +
105 std::to_string(getLine()) + " of " + getPath() + ".");
106 }
107}
108
109std::tuple<std::string, std::string, int>
110XLIFFParser::parseSimpleTransUnitId(const std::string& id)
111{
112 std::regex simpleIdRegexp(R"(^([^/:]+)/([^/:]+):(\d+)$)");
113 std::smatch results;
114
115 if (std::regex_match(id, results, simpleIdRegexp)) {
116 const int elementIdx = strutils::readNonNegativeInt<int>(results.str(3));
117 return std::make_tuple(results.str(1), results.str(2), elementIdx);
118 } else {
120 "Error while parsing a .xlf file",
121 "Unexpected 'id' attribute value in a <trans-unit> or <group>.",
122 "Unexpected syntax for a <trans-unit> or "
123 "<group restype=\"x-gettext-plurals\" ...> 'id' attribute: '" +
124 id + "' at " + std::to_string(getLine()) + " of " + getPath() + ".");
125 }
126}
127
129{
130 std::string tag(name);
131
132 if (tag == "file") {
133 _skipElements = false;
134 } else if (_skipElements) {
135 return;
136 } else if (tag == "source") {
137 _sourceText = _text;
138 } else if (tag == "target") {
139 _targetTexts.push_back(std::move(_text));
140 } else if (tag == "trans-unit") {
141 if (_pluralGroupId.empty()) { // not inside a plural group
142 finishTransUnit(false /* hasPlural */);
143 }
144 } else if (tag == "group") {
145 assert(_groupsStack.size() > 0);
146
147 switch (_groupsStack.top()->type) {
148 case GroupType::context:
149 endContextGroup();
150 break;
151 case GroupType::plural:
152 endPluralGroup();
153 break;
154 default:
155 std::abort();
156 }
157 }
158}
159
160void XLIFFParser::startContextGroup(const char* resname_c)
161{
162 if (resname_c == nullptr) {
163 SG_LOG(SG_GENERAL, SG_WARN,
164 "XLIFF group with restype=\"x-trolltech-linguist-context\" has "
165 "no 'resname' attribute: line " << getLine() << " of " <<
166 getPath());
167 return;
168 }
169
170 const std::string resname{resname_c};
171
172 if (resname.empty()) {
173 SG_LOG(SG_GENERAL, SG_WARN,
174 "XLIFF group with restype=\"x-trolltech-linguist-context\" has "
175 "an empty 'resname' attribute: line " << getLine() << " of " <<
176 getPath());
177 return;
178 }
179
180 _resource = resname;
181 // This is where the strings will be stored. getOrCreateResource()
182 // creates the TranslationResource if necessary.
183 _currentResource = _domain->getOrCreateResource(resname);
184 _groupsStack.push(std::make_unique<ContextGroup>(resname));
185}
186
187void XLIFFParser::startPluralGroup(const char* id_c)
188{
189 if (id_c == nullptr) {
190 SG_LOG(SG_GENERAL, SG_WARN,
191 "XLIFF group with restype=\"x-gettext-plurals\" has "
192 "no 'id' attribute: at line " << getLine() << " of " <<
193 getPath());
194 return;
195 }
196
197 // Instance member _resource was set when the context group was started.
198 std::string resource;
199 std::tie(resource, _basicId, _index) = parseSimpleTransUnitId(id_c);
200
201 if (resource != _resource) {
203 "Error while parsing a .xlf file",
204 "Unexpected 'id' attribute value in a <group "
205 "restype=\"x-gettext-plurals\" ...> element.",
206 "Attribute 'id' of a <group restype=\"x-gettext-plurals\" ...> "
207 "element inside plural group '" + _pluralGroupId +
208 "' specifies resource '" + resource + "' whereas "
209 "the current context group declares resname='" + _resource + "' "
210 "(attribute id='" + std::string(id_c) + "' at line " +
211 std::to_string(getLine()) + " of " + getPath() + ").");
212 }
213
214 _pluralGroupId = id_c;
215 _expectedPluralFormIndex = 0; // next <trans-unit> is for plural form 0
216 _groupsStack.push(std::make_unique<PluralGroup>(id_c));
217}
218
219void XLIFFParser::endContextGroup()
220{
221 assert(dynamic_cast<ContextGroup*>(_groupsStack.top().get())->name
222 == _resource);
223
224 _groupsStack.pop();
225 _resource.clear();
226 _currentResource.reset();
227}
228
229void XLIFFParser::endPluralGroup()
230{
231 assert(dynamic_cast<PluralGroup*>(_groupsStack.top().get())->id
232 == _pluralGroupId);
233
234 finishTransUnit(true /* hasPlural */);
235
236 _groupsStack.pop();
237 _pluralGroupId.clear();
238}
239
240void XLIFFParser::finishTransUnit(bool hasPlural)
241{
242 if (!_currentResource) {
243 SG_LOG(SG_GENERAL, SG_WARN,
244 "XLIFF trans-unit without enclosing resource <group>: at line "
245 << getLine() << " of " << getPath());
246 } else if (hasPlural) {
247 checkNumberOfPluralForms(_targetTexts.size());
248 _currentResource->setTargetTexts(std::move(_basicId), _index,
249 std::move(_targetTexts));
250 } else if (!_targetTexts.empty()) { // the 'target' element is optional
251 _currentResource->setFirstTargetText(std::move(_basicId), _index,
252 std::move(_targetTexts[0]));
253 }
254
255 _sourceText.clear();
256 _targetTexts.clear();
257}
258
259void XLIFFParser::checkNumberOfPluralForms(std::size_t nbPluralFormsInTransUnit)
260{
261 const std::size_t nbPluralFormsInCode =
263
264 if (nbPluralFormsInTransUnit != nbPluralFormsInCode) {
266 "Error while parsing a .xlf file",
267 "Mismatch between the number of plural forms found in a "
268 "group with restype=\"x-gettext-plurals\" and the number of "
269 "plural forms declared in LanguageInfo.cxx for language '" +
270 _languageId + "'.",
271 "Found a plural group with " +
272 std::to_string(nbPluralFormsInTransUnit) + " plural forms, however "
273 "the number of plural forms for this language as set in "
274 "LanguageInfo.cxx is " + std::to_string(nbPluralFormsInCode) +
275 " (at " + std::to_string(getLine()) + " of " + getPath() + ").");
276 }
277}
278
279void XLIFFParser::data (const char * s, int len)
280{
281 _text += std::string(s, static_cast<size_t>(len));
282}
283
284
285void XLIFFParser::pi (const char * target, const char * data)
286{
287 SG_UNUSED(target);
288 SG_UNUSED(data);
289 //cout << "Processing instruction " << target << ' ' << data << endl;
290}
291
292void XLIFFParser::warning (const char * message, int line, int column) {
293 SG_LOG(SG_GENERAL, SG_WARN, "Warning: " << message << " (" << line << ',' << column << ')');
294}
295
296// For the elements of the <group> stack
297XLIFFParser::Group::Group(GroupType type_) : type(type_)
298{ }
299
300XLIFFParser::ContextGroup::ContextGroup(const std::string& name_)
301 : Group(GroupType::context), name(name_)
302{ }
303
304XLIFFParser::PluralGroup::PluralGroup(const std::string& id_)
305 : Group(GroupType::plural), id(id_)
306{ }
Information on plural forms for the supported languages.
Parse an XLIFF 1.2 XML file.
static std::size_t getNumberOfPluralForms(const std::string &languageId)
Return the number of plural forms in the specified language.
Class that holds translation resources within a domain.
void data(const char *s, int len) override
void endXML() override
void pi(const char *target, const char *data) override
void startXML() override
XLIFFParser(const std::string &languageId, TranslationDomain *domain)
void endElement(const char *name) override
void startElement(const char *name, const XMLAttributes &atts) override
void warning(const char *message, int line, int column) override
const char * name
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
const char * name
void fatalMessageBoxThenExit(const std::string &caption, const std::string &msg, const std::string &moreText, int exitStatus, bool reportToSentry)