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 ("&amp;", "&");
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