1 /***************************************************************************
2 xmlhelper.cpp - description
3 -------------------
4 begin : Fri May 6 2005
5 copyright : (C) 2005, 2007, 2011, 2014 by Thomas Friedrichsmeier
6 email : thomas.friedrichsmeier@kdemail.net
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "xmlhelper.h"
19
20 #include <KLocalizedString>
21
22 #include <qstringlist.h>
23 #include <qfile.h>
24 #include <qfileinfo.h>
25 #include <qdir.h>
26 #include <QTextStream>
27
28 #include <rkmessagecatalog.h>
29
30 #include "../debug.h"
31
XMLHelper(const QString & filename,const RKMessageCatalog * default_catalog)32 XMLHelper::XMLHelper (const QString &filename, const RKMessageCatalog *default_catalog) {
33 RK_TRACE (XML);
34 XMLHelper::filename = filename;
35 if (!default_catalog) catalog = RKMessageCatalog::nullCatalog ();
36 else catalog = default_catalog;
37 }
38
~XMLHelper()39 XMLHelper::~XMLHelper () {
40 RK_TRACE (XML);
41 }
42
openXMLFile(int debug_level,bool with_includes,bool with_snippets)43 QDomElement XMLHelper::openXMLFile (int debug_level, bool with_includes, bool with_snippets) {
44 RK_TRACE (XML);
45
46 int error_line, error_column;
47 QString error_message;
48 QDomDocument doc;
49
50 QFile f (filename);
51 if (!f.open (QIODevice::ReadOnly)) displayError (0, i18n("Could not open file %1 for reading", filename), debug_level, DL_ERROR);
52 if (!doc.setContent(&f, false, &error_message, &error_line, &error_column)) {
53 displayError (0, i18n ("Error parsing XML-file. Error-message was: '%1' in line '%2', column '%3'. Expect further errors to be reported below", error_message, error_line, error_column), debug_level, DL_ERROR);
54 return QDomElement ();
55 }
56 f.close();
57
58 QDomElement ret = doc.documentElement ();
59 if (ret.hasAttribute ("po_id")) {
60 QDir path = QFileInfo (filename).absoluteDir ();
61 catalog = RKMessageCatalog::getCatalog ("rkward__" + ret.attribute ("po_id"), path.absoluteFilePath (getStringAttribute (ret, "po_path", "po", DL_INFO)));
62 }
63 if (with_includes) {
64 XMLChildList includes = nodeListToChildList (doc.elementsByTagName ("include"));
65 for (XMLChildList::const_iterator it = includes.constBegin (); it != includes.constEnd (); ++it) {
66 // resolve the file to include
67 QDomElement el = *it;
68
69 QString inc_filename = getStringAttribute (el, "file", QString (), DL_ERROR);
70 QDir base = QFileInfo (filename).absoluteDir ();
71 inc_filename = base.filePath (inc_filename);
72
73 // import
74 XMLHelper inc_xml = XMLHelper (inc_filename);
75 QDomElement included = inc_xml.openXMLFile (debug_level, true, false);
76
77 if (!included.isNull ()) {
78 QDomElement copied = doc.importNode (included, true).toElement ();
79
80 // insert everything within the document tag
81 replaceWithChildren (&el, copied);
82 }
83 }
84 }
85
86 if (with_snippets) {
87 return (resolveSnippets (ret));
88 }
89
90 return (ret);
91 }
92
replaceWithChildren(QDomNode * replaced,const QDomElement & replacement_parent)93 void XMLHelper::replaceWithChildren (QDomNode *replaced, const QDomElement &replacement_parent) {
94 RK_TRACE (XML);
95 RK_ASSERT (replaced);
96
97 QDomNode parent = replaced->parentNode ();
98 XMLChildList replacement_children = getChildElements (replacement_parent, QString (), DL_WARNING);
99 for (XMLChildList::const_iterator it = replacement_children.constBegin (); it != replacement_children.constEnd (); ++it) {
100 parent.insertBefore (*it, *replaced);
101 }
102 parent.removeChild (*replaced);
103 }
104
nodeListToChildList(const QDomNodeList & from)105 XMLChildList XMLHelper::nodeListToChildList (const QDomNodeList &from) {
106 RK_TRACE (XML);
107
108 int count = from.count ();
109 XMLChildList ret;
110 for (int i = 0; i < count; ++i) {
111 ret.append (from.item (i).toElement ());
112 }
113 return ret;
114 }
115
resolveSnippets(QDomElement & from_doc)116 QDomElement XMLHelper::resolveSnippets (QDomElement &from_doc) {
117 RK_TRACE (XML);
118
119 XMLChildList refs = nodeListToChildList (from_doc.elementsByTagName ("insert"));
120 int ref_count = refs.count ();
121
122 if (!ref_count) { // nothing to resolve
123 return (from_doc);
124 }
125
126 QDomElement snippets_section = getChildElement (from_doc, "snippets", DL_ERROR);
127 XMLChildList snippets = getChildElements (snippets_section, "snippet", DL_ERROR);
128
129 for (XMLChildList::const_iterator it = refs.constBegin (); it != refs.constEnd (); ++it) {
130 QDomElement ref = *it;
131 QString id = getStringAttribute (ref, "snippet", QString (), DL_ERROR);
132 displayError (&ref, "resolving snippet '" + id + '\'', DL_DEBUG, DL_DEBUG);
133
134 // resolve the reference
135 QDomElement snippet;
136 for (XMLChildList::const_iterator it = snippets.constBegin(); it != snippets.constEnd (); ++it) {
137 if (getStringAttribute (*it, "id", QString (), DL_ERROR) == id) {
138 snippet = *it;
139 break;
140 }
141 }
142 if (snippet.isNull ()) {
143 displayError (&ref, "no such snippet '" + id + '\'', DL_ERROR, DL_ERROR);
144 }
145
146 // now insert it.
147 replaceWithChildren (&ref, snippet.cloneNode (true).toElement ());
148 }
149
150 return from_doc;
151 }
152
getChildElements(const QDomElement & parent,const QString & name,int debug_level)153 XMLChildList XMLHelper::getChildElements (const QDomElement &parent, const QString &name, int debug_level) {
154 RK_TRACE (XML);
155
156 XMLChildList list;
157
158 if (!parent.isNull()) {
159 QDomNode n = parent.firstChild ();
160 while (!n.isNull ()) {
161 QDomElement e = n.toElement ();
162 if (!e.isNull ()) {
163 if ((name.isEmpty ()) || (e.tagName () == name)) {
164 list.append (e);
165 }
166 }
167 n = n.nextSibling ();
168 }
169 } else {
170 displayError (&parent, i18n ("Trying to retrieve children of invalid element"), debug_level);
171 }
172
173 return (list);
174 }
175
getChildElement(const QDomElement & parent,const QString & name,int debug_level)176 QDomElement XMLHelper::getChildElement (const QDomElement &parent, const QString &name, int debug_level) {
177 RK_TRACE (XML);
178
179 XMLChildList list = getChildElements (parent, name, debug_level);
180 if (list.count () != 1) {
181 displayError (&parent, i18n ("Expected exactly one element '%1' but found %2", name, list.count ()), debug_level);
182 QDomElement dummy;
183 return dummy;
184 }
185
186 return list.first ();
187 }
188
findElementWithAttribute(const QDomElement & parent,const QString & attribute_name,const QString & attribute_value,bool recursive,int debug_level)189 QDomElement XMLHelper::findElementWithAttribute (const QDomElement &parent, const QString &attribute_name, const QString &attribute_value, bool recursive, int debug_level) {
190 RK_TRACE (XML);
191
192 XMLChildList list = getChildElements (parent, QString (), debug_level);
193 for (XMLChildList::const_iterator it = list.constBegin (); it != list.constEnd (); ++it) {
194 if ((*it).hasAttribute (attribute_name)) {
195 if (attribute_value.isNull () || ((*it).attribute (attribute_name) == attribute_value)) {
196 return (*it);
197 }
198 }
199 if (recursive) {
200 QDomElement found = findElementWithAttribute (*it, attribute_name, attribute_value, true, debug_level);
201 if (!found.isNull ()) return found;
202 }
203 }
204
205 QDomElement dummy;
206 return dummy;
207 }
208
findElementsWithAttribute(const QDomElement & parent,const QString & attribute_name,const QString & attribute_value,bool recursive,int debug_level)209 XMLChildList XMLHelper::findElementsWithAttribute (const QDomElement &parent, const QString &attribute_name, const QString &attribute_value, bool recursive, int debug_level) {
210 RK_TRACE (XML);
211
212 XMLChildList ret;
213 XMLChildList list = getChildElements (parent, QString (), debug_level);
214 for (XMLChildList::const_iterator it = list.constBegin (); it != list.constEnd (); ++it) {
215 if ((*it).hasAttribute (attribute_name)) {
216 if (attribute_value.isNull () || ((*it).attribute (attribute_name) == attribute_value)) {
217 ret.append (*it);
218 }
219 }
220 if (recursive) {
221 XMLChildList subret = findElementsWithAttribute (*it, attribute_name, attribute_value, true, debug_level);
222 for (XMLChildList::const_iterator it = subret.constBegin (); it != subret.constEnd (); ++it) {
223 ret.append (*it);
224 }
225 }
226 }
227
228 return ret;
229 }
230
getStringAttribute(const QDomElement & element,const QString & name,const QString & def,int debug_level)231 QString XMLHelper::getStringAttribute (const QDomElement &element, const QString &name, const QString &def, int debug_level) {
232 RK_TRACE (XML);
233
234 if (!element.hasAttribute (name)) {
235 displayError (&element, i18n ("'%1'-attribute not given. Assuming '%2'", name, def), debug_level);
236 return (def);
237 }
238
239 return (element.attribute (name));
240 }
241
i18nStringAttribute(const QDomElement & element,const QString & name,const QString & def,int debug_level)242 QString XMLHelper::i18nStringAttribute (const QDomElement& element, const QString& name, const QString& def, int debug_level) {
243 RK_TRACE (XML);
244 if (!element.hasAttribute (name)) {
245 const QString no18nname = "noi18n_" + name;
246 if (element.hasAttribute (no18nname)) return element.attribute (no18nname);
247 displayError (&element, i18n ("'%1'-attribute not given. Assuming '%2'", name, def), debug_level);
248 return def;
249 }
250
251 QString attr = element.attribute (name);
252 if (attr.isEmpty ()) return attr; // Do not translate empty strings!
253
254 const QString context = element.attribute ("i18n_context", QString ());
255 if (!context.isNull ()) return (catalog->translate (context, attr));
256 return (catalog->translate (attr));
257 }
258
getMultiChoiceAttribute(const QDomElement & element,const QString & name,const QString & values,int def,int debug_level)259 int XMLHelper::getMultiChoiceAttribute (const QDomElement &element, const QString &name, const QString &values, int def, int debug_level) {
260 RK_TRACE (XML);
261
262 QStringList value_list = values.split (';');
263
264 QString plain_value = getStringAttribute (element, name, value_list[def], debug_level);
265
266 int index;
267 if ((index = value_list.indexOf (plain_value)) >= 0) {
268 return (index);
269 } else {
270 displayError (&element, i18n ("Illegal attribute value. Allowed values are one of '%1', only.", values), debug_level, DL_ERROR);
271 return def;
272 }
273 }
274
getIntAttribute(const QDomElement & element,const QString & name,int def,int debug_level)275 int XMLHelper::getIntAttribute (const QDomElement &element, const QString &name, int def, int debug_level) {
276 RK_TRACE (XML);
277
278 QString res = getStringAttribute (element, name, QString::number (def), debug_level);
279
280 bool valid_number;
281 int ret = res.toInt (&valid_number);
282
283 if (!valid_number) {
284 displayError (&element, i18n ("Illegal attribute value. Only integer numbers are allowed."), debug_level, DL_ERROR);
285 return def;
286 }
287
288 return ret;
289 }
290
getDoubleAttribute(const QDomElement & element,const QString & name,double def,int debug_level)291 double XMLHelper::getDoubleAttribute (const QDomElement &element, const QString &name, double def, int debug_level) {
292 RK_TRACE (XML);
293
294 QString res = getStringAttribute (element, name, QString::number (def), debug_level);
295
296 bool valid_number;
297 double ret = res.toDouble (&valid_number);
298
299 if (!valid_number) {
300 displayError (&element, i18n ("Illegal attribute value. Only real numbers are allowed."), debug_level, DL_ERROR);
301 return def;
302 }
303
304 return ret;
305 }
306
getBoolAttribute(const QDomElement & element,const QString & name,bool def,int debug_level)307 bool XMLHelper::getBoolAttribute (const QDomElement &element, const QString &name, bool def, int debug_level) {
308 RK_TRACE (XML);
309
310 QString defstring, res;
311 if (def) defstring = "true";
312 else defstring = "false";
313
314 res = getStringAttribute (element, name, defstring, debug_level);
315 if (res == "true") return true;
316 if (res == "false") return false;
317
318 displayError (&element, i18n ("Illegal attribute value. Allowed values are '%1' or '%2', only.", QString ("true"), QString ("false")), debug_level, DL_ERROR);
319 return def;
320 }
321
translateChunk(const QString & chunk,const QString & context,bool add_paragraphs,const RKMessageCatalog * catalog)322 QString translateChunk (const QString &chunk, const QString &context, bool add_paragraphs, const RKMessageCatalog *catalog) {
323 // if (!with_paragraphs), text should better not contain double newlines. We treat all the same, though, just as the message extraction script does.
324 QStringList paras = chunk.split ("\n\n");
325 QString ret;
326
327 for (int i = 0; i < paras.count (); ++i) {
328 QString para = paras[i].simplified ();
329 if (para.isEmpty ()) ret.append (QChar (' '));
330 else {
331 if (!ret.isEmpty ()) ret.append ("\n");
332 // Oh, crap. Fix up after some differences between python message extraction and qt's.
333 para.replace ("<li> <", "<li><");
334 para.replace ("br> <", "br><");
335 para.replace ("> </li>", "></li>");
336 para.replace ("&", "&");
337 QString text = context.isNull () ? catalog->translate (para) : catalog->translate (context, para);
338 if (add_paragraphs) ret += "<p>" + text + "</p>";
339 else ret += text;
340 }
341 }
342
343 return ret;
344 }
345
i18nElementText(const QDomElement & element,bool with_paragraphs,int debug_level) const346 QString XMLHelper::i18nElementText (const QDomElement &element, bool with_paragraphs, int debug_level) const {
347 RK_TRACE (XML);
348
349 if (element.isNull ()) {
350 displayError (&element, i18n ("Trying to retrieve contents of invalid element"), debug_level);
351 return QString ();
352 }
353
354 QString ret;
355 QString context = element.attribute ("i18n_context", QString ());
356 QString buffer;
357 QTextStream stream (&buffer, QIODevice::WriteOnly);
358 for (QDomNode node = element.firstChild (); !node.isNull (); node = node.nextSibling ()) {
359 QDomElement e = node.toElement ();
360 if (!e.isNull ()) {
361 if (e.tagName () == QLatin1String ("ul") || e.tagName () == QLatin1String ("ol") || e.tagName () == QLatin1String ("li") || e.tagName () == QLatin1String ("p")) { // split translation units on these elements
362 // split before
363 ret.append (translateChunk (buffer, context, with_paragraphs, catalog));
364 buffer.clear ();
365
366 // serialize the tag with all its attributes but not the children.
367 e.cloneNode (false).save (stream, 0); // will write: <TAG[ attributes]/>
368 buffer = buffer.left (buffer.lastIndexOf ('/')) + QChar ('>');
369 buffer.append (i18nElementText (e, false, debug_level));
370 buffer.append ("</" + e.tagName () + QChar ('>'));
371
372 // split after
373 ret.append (buffer);
374 buffer.clear ();
375 continue;
376 }
377 }
378 node.save (stream, 0);
379 }
380 ret.append (translateChunk (buffer, context, with_paragraphs, catalog));
381
382 return ret;
383 }
384
displayError(const QDomNode * in_node,const QString & message,int debug_level,int message_level) const385 void XMLHelper::displayError (const QDomNode *in_node, const QString &message, int debug_level, int message_level) const {
386 RK_TRACE (XML);
387
388 if (message_level < debug_level) message_level = debug_level;
389
390 if ((RK_Debug::RK_Debug_Flags & XML) && (message_level >= RK_Debug::RK_Debug_Level)) {
391 QString backtrace = i18n ("XML-parsing '%1' ", filename);
392 // create a "backtrace"
393 QStringList list;
394
395 if (in_node) {
396 QDomNode node_copy = *in_node;
397 while (!((node_copy.isNull ()) || (node_copy.isDocument ()))) {
398 list.prepend (node_copy.nodeName ());
399 node_copy = node_copy.parentNode ();
400 }
401 }
402
403 backtrace += list.join ("->");
404
405 RK_DEBUG (XML, message_level, "%s: %s", backtrace.toLatin1 ().data (), message.toLatin1 ().data ());
406 }
407 }
408
409