1 //=============================================================================
2 //
3 //   File : KvsObject_xmlreader.cpp
4 //   Created on Tue 27 Dec 2005 00:14:09 by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC Client distribution
7 //   Copyright (C) 2005-2010 Szymon Stefanek <pragma at kvirc dot net>
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 #include "KvsObject_xmlreader.h"
26 #include "KvsObject_memoryBuffer.h"
27 #include "KviLocale.h"
28 #include "KviKvsVariantList.h"
29 #include "KviKvsHash.h"
30 
31 /*
32 	@doc: xmlreader
33 	@keyterms:
34 		xml
35 	@title:
36 		xmlreader class
37 	@type:
38 		class
39 	@short:
40 		A simple XML document parser
41 	@inherits:
42 		[class]object[/class]
43 	@description:
44 		This class implements a really simple XML document parser.
45 		You will usually derive your own class from this one and reimplement
46 		some of the events that it triggers.
47 		You will typically reimplement [classfnc:xmlreader]$onElementStart[/classfnc]()
48 		and [classfnc:xmlreader]$onElementEnd[/classfnc]() that will be called
49 		during the execution of [classfnc:xmlreader]$parse[/classfnc]() in an order
50 		reflecting the order of elements in the parsed document.
51 	@functions:
52 		!fn: <boolean> $parse(<xml_data:string_or_memorybuffer_object>)
53 		Call this function to parse a string that contains an XML document.
54 		A typical call for this method will look like:
55 		[example]
56 			%x = [fnc]$new[/fnc](xmlreader)
57 			%x->$parse([fnc]$file.read[/fnc]("/home/somefile.xml"))
58 		[/example]
59 		During the call the <xml_data> string will be parsed and the
60 		relevant on* events (see below) will be triggered.
61 		$parse will return $true when the parsing terminates successfully
62 		or $false if it aborts for some reason (unrecoverable error
63 		in the document, user abort etc.).
64 		If this function return $false then you can call $lastError() to
65 		obtain a descriptive error message.
66 		!fn: <string> $lastError()
67 		Returns the last error occurred inside the parser.
68 		You will typically call this function when $parse() above returns $false.
69 		!fn: <boolean> $onDocumentStart()
70 		This function is called when the document parsing starts.
71 		You can reimplement it in order to handle this notification.
72 		You should return $true if you want document parsing to continue
73 		and $false if you want it to be aborted.
74 		The default implementation does nothing besides returning $true.
75 		!fn: <boolean> $onDocumentEnd()
76 		This function is called when the document parsing terminates successfully.
77 		You can reimplement it in order to handle this notification.
78 		You should return $true if you want document parsing to continue
79 		and $false if you want it to be aborted.
80 		The default implementation does nothing besides returning $true.
81 		!fn: <boolean> $onElementStart(<qualified_name:string>,<attributes:hash>,<namespace:string>,<local_name:string>)
82 		This function is called when an element opening tag is encountered.
83 		The <qualified_name> of the tag is passed as the first parameter.
84 		The <attributes> are passed in the form of a hash with attribute
85 		values indexed by their names.
86 		When the <qualified_name> contains a namespace then it is also reported
87 		in the split <namespace> <local_name> pair.
88 		You should return $true if you want document parsing to continue
89 		and $false if you want it to be aborted.
90 		The default implementation does nothing besides returning $true.
91 		!fn: <boolean> $onElementEnd(<qualified_name:string>,<namespace:string>,<local_name:string>)
92 		This function is called when an element closing tag is encountered.
93 		The <qualified_name> of the tag is passed as the first parameter.
94 		When the <qualified_name> contains a namespace then it is also reported
95 		in the split <namespace> <local_name> pair.
96 		You should return $true if you want document parsing to continue
97 		and $false if you want it to be aborted.
98 		The default implementation does nothing besides returning $true.
99 		!fn: <boolean> $onText($0 = <text:string>)
100 		This function is called when a chunk of text is encountered inside the document.
101 		The parsed <text> chunk is passed as the first parameter.
102 		You should return $true if you want document parsing to continue
103 		and $false if you want it to be aborted.
104 		The default implementation does nothing besides returning $true.
105 		!fn: <boolean> $onWarning(<message:string>)
106 		This function is called when the parser generates a recoverable error.
107 		The error <message> is passed as the first parameter.
108 		You should return $true if you want document parsing to continue
109 		and $false if you want it to be aborted.
110 		The default implementation does nothing besides returning $true.
111 		!fn: <boolean> $onError(<message:string>)
112 		This function is called when the parser generates an unrecoverable error.
113 		The error <message> is passed as the first parameter.
114 		The document parsing can't continue.
115 		The default implementation does nothing besides returning $true.
116 */
117 
118 #ifndef QT_NO_XML
119 
120 #include <qxml.h>
121 
122 class KviXmlHandler : public QXmlDefaultHandler
123 {
124 protected:
125 	KvsObject_xmlReader * m_pReader;
126 	QString m_szErrorString;
127 
128 public:
KviXmlHandler(KvsObject_xmlReader * pReader)129 	KviXmlHandler(KvsObject_xmlReader * pReader)
130 	{
131 		m_pReader = pReader;
132 	}
133 	~KviXmlHandler() override = default;
134 
135 private:
kvsCodeFailure()136 	bool kvsCodeFailure()
137 	{
138 		m_szErrorString = __tr2qs_ctx("Error in KVS class implementation: processing aborted", "objects");
139 		return false;
140 	}
kvsCodeAbort()141 	bool kvsCodeAbort()
142 	{
143 		m_szErrorString = __tr2qs_ctx("Processing aborted", "objects");
144 		return false;
145 	}
decodeException(QString & szMsg,bool bError,const QXmlParseException & exception)146 	void decodeException(QString & szMsg, bool bError, const QXmlParseException & exception)
147 	{
148 		if(bError)
149 			szMsg = QString(__tr2qs_ctx("Error near line %1, column %2", "objects")).arg(exception.lineNumber()).arg(exception.columnNumber());
150 		else
151 			szMsg = QString(__tr2qs_ctx("Warning near line %1, column %2", "objects")).arg(exception.lineNumber()).arg(exception.columnNumber());
152 		szMsg += ": ";
153 		szMsg += exception.message();
154 	}
handleKvsCallReturnValue(KviKvsVariant * pRetVal)155 	bool handleKvsCallReturnValue(KviKvsVariant * pRetVal)
156 	{
157 		if(!pRetVal->asBoolean())
158 			return kvsCodeAbort();
159 		return true;
160 	}
161 
162 public:
startDocument()163 	bool startDocument() override
164 	{
165 		KviKvsVariant ret;
166 		if(!m_pReader->callFunction(m_pReader, "onDocumentStart", &ret))
167 			return kvsCodeFailure();
168 		return handleKvsCallReturnValue(&ret);
169 	}
170 
endDocument()171 	bool endDocument() override
172 	{
173 		KviKvsVariant ret;
174 		if(!m_pReader->callFunction(m_pReader, "onDocumentEnd", &ret))
175 			return kvsCodeFailure();
176 		return handleKvsCallReturnValue(&ret);
177 	}
178 
startElement(const QString & szNamespaceUri,const QString & szLocalName,const QString & szQualifiedName,const QXmlAttributes & attrs)179 	bool startElement(const QString & szNamespaceUri, const QString & szLocalName, const QString & szQualifiedName, const QXmlAttributes & attrs) override
180 	{
181 		KviKvsVariant ret;
182 		KviKvsVariantList par;
183 		par.setAutoDelete(true);
184 		par.append(new KviKvsVariant(szQualifiedName));
185 		KviKvsHash * pHash = new KviKvsHash();
186 		par.append(new KviKvsVariant(pHash));
187 		par.append(new KviKvsVariant(szNamespaceUri));
188 		par.append(new KviKvsVariant(szLocalName));
189 		int c = attrs.count();
190 		for(int i = 0; i < c; i++)
191 			pHash->set(attrs.qName(i), new KviKvsVariant(attrs.value(i)));
192 		if(!m_pReader->callFunction(m_pReader, "onElementStart", &ret, &par))
193 			return kvsCodeFailure();
194 		return handleKvsCallReturnValue(&ret);
195 	}
196 
endElement(const QString & szNamespaceUri,const QString & szLocalName,const QString & szQualifiedName)197 	bool endElement(const QString & szNamespaceUri, const QString & szLocalName, const QString & szQualifiedName) override
198 	{
199 		KviKvsVariant ret;
200 		KviKvsVariantList par;
201 		par.setAutoDelete(true);
202 		par.append(new KviKvsVariant(szQualifiedName));
203 		par.append(new KviKvsVariant(szNamespaceUri));
204 		par.append(new KviKvsVariant(szLocalName));
205 		if(!m_pReader->callFunction(m_pReader, "onElementEnd", &ret, &par))
206 			return kvsCodeFailure();
207 		return handleKvsCallReturnValue(&ret);
208 	}
209 
characters(const QString & szChars)210 	bool characters(const QString & szChars) override
211 	{
212 		KviKvsVariant ret;
213 		KviKvsVariantList par;
214 		par.setAutoDelete(true);
215 		par.append(new KviKvsVariant(szChars));
216 		if(!m_pReader->callFunction(m_pReader, "onText", &ret, &par))
217 			return kvsCodeFailure();
218 		return handleKvsCallReturnValue(&ret);
219 	}
220 
warning(const QXmlParseException & exception)221 	bool warning(const QXmlParseException & exception) override
222 	{
223 		// recoverable
224 		QString szMsg;
225 		decodeException(szMsg, false, exception);
226 
227 		KviKvsVariant ret;
228 		KviKvsVariantList par;
229 		par.setAutoDelete(true);
230 		par.append(new KviKvsVariant(szMsg));
231 		if(!m_pReader->callFunction(m_pReader, "onWarning", &ret, &par))
232 			return kvsCodeFailure();
233 		return handleKvsCallReturnValue(&ret);
234 	}
235 
error(const QXmlParseException & exception)236 	bool error(const QXmlParseException & exception) override
237 	{
238 		// recoverable
239 		QString szMsg;
240 		decodeException(szMsg, false, exception);
241 
242 		KviKvsVariant ret;
243 		KviKvsVariantList par;
244 		par.setAutoDelete(true);
245 		par.append(new KviKvsVariant(szMsg));
246 		if(!m_pReader->callFunction(m_pReader, "onWarning", &ret, &par))
247 			return kvsCodeFailure();
248 		return handleKvsCallReturnValue(&ret);
249 	}
250 
fatalError(const QXmlParseException & exception)251 	bool fatalError(const QXmlParseException & exception) override
252 	{
253 		QString szMsg;
254 		decodeException(szMsg, true, exception);
255 		m_pReader->fatalError(szMsg);
256 		return true;
257 	}
258 
errorString() const259 	QString errorString() const override
260 	{
261 		return m_szErrorString;
262 	}
263 };
264 
265 #endif // !QT_NO_XML
266 
267 KVSO_BEGIN_REGISTERCLASS(KvsObject_xmlReader, "xmlreader", "object")
KVSO_REGISTER_HANDLER_BY_NAME(KvsObject_xmlReader,lastError)268 KVSO_REGISTER_HANDLER_BY_NAME(KvsObject_xmlReader, lastError)
269 KVSO_REGISTER_HANDLER_BY_NAME(KvsObject_xmlReader, parse)
270 
271 KVSO_REGISTER_STANDARD_TRUERETURN_HANDLER(KvsObject_xmlReader, "onDocumentStart")
272 KVSO_REGISTER_STANDARD_TRUERETURN_HANDLER(KvsObject_xmlReader, "onDocumentEnd")
273 KVSO_REGISTER_STANDARD_TRUERETURN_HANDLER(KvsObject_xmlReader, "onElementStart")
274 KVSO_REGISTER_STANDARD_TRUERETURN_HANDLER(KvsObject_xmlReader, "onElementEnd")
275 KVSO_REGISTER_STANDARD_TRUERETURN_HANDLER(KvsObject_xmlReader, "onText")
276 KVSO_REGISTER_STANDARD_TRUERETURN_HANDLER(KvsObject_xmlReader, "onWarning")
277 KVSO_REGISTER_STANDARD_TRUERETURN_HANDLER(KvsObject_xmlReader, "onError")
278 KVSO_END_REGISTERCLASS(KvsObject_xmlReader)
279 
280 KVSO_BEGIN_CONSTRUCTOR(KvsObject_xmlReader, KviKvsObject)
281 KVSO_END_CONSTRUCTOR(KvsObject_xmlReader)
282 
283 KVSO_BEGIN_DESTRUCTOR(KvsObject_xmlReader)
284 KVSO_END_DESTRUCTOR(KvsObject_xmlReader)
285 
286 void KvsObject_xmlReader::fatalError(const QString & szError)
287 {
288 	m_szLastError = szError;
289 
290 	KviKvsVariantList vArgs;
291 	vArgs.append(new KviKvsVariant(m_szLastError));
292 	callFunction(this, "onError", &vArgs);
293 }
294 
KVSO_CLASS_FUNCTION(xmlReader,parse)295 KVSO_CLASS_FUNCTION(xmlReader, parse)
296 {
297 	KviKvsVariant * pVariantData;
298 
299 	KVSO_PARAMETERS_BEGIN(c)
300 	KVSO_PARAMETER("string_or_memorybuffer_object", KVS_PT_VARIANT, 0, pVariantData)
301 	KVSO_PARAMETERS_END(c)
302 #ifdef QT_NO_XML
303 	fatalError(__tr2qs_ctx("XML support not available in the Qt library"));
304 	c->returnValue()->setBoolean(false);
305 #else
306 	m_szLastError = "";
307 	KviXmlHandler handler(this);
308 	QXmlInputSource source;
309 
310 	if(pVariantData->isHObject())
311 	{
312 		KviKvsObject * pObject;
313 		kvs_hobject_t hObject;
314 		pVariantData->asHObject(hObject);
315 		pObject = KviKvsKernel::instance()->objectController()->lookupObject(hObject);
316 		if(!pObject)
317 		{
318 			c->warning(__tr2qs_ctx("Data parameter is not an object", "objects"));
319 			return true;
320 		}
321 		if(pObject->inheritsClass("memorybuffer"))
322 		{
323 			source.setData(*((KvsObject_memoryBuffer *)pObject)->pBuffer());
324 		}
325 		else
326 		{
327 			c->warning(__tr2qs_ctx("Data parameter is not a memorybuffer object", "objects"));
328 			return true;
329 		}
330 	}
331 	else if(pVariantData->isString())
332 	{
333 		QString szString;
334 		pVariantData->asString(szString);
335 		// We have a problem here.. most kvirc functions already interpret the data
336 		// read from files. We should have binary data handling features to get this to work correctly.
337 		// The following snippet of code tries to provide a best-effort workaround.
338 		QByteArray utf8data = szString.toUtf8();
339 		QByteArray data = utf8data;
340 		data.truncate(utf8data.length()); // don't include the null terminator in data
341 		source.setData(data);
342 		//qDebug("PARSING(%s) LEN(%d)",szString.toUtf8().data(),szString.toUtf8().length());
343 	}
344 	else
345 	{
346 		c->warning(__tr2qs_ctx("Data is not a memorybuffer object or string", "objects"));
347 		return true;
348 	}
349 	QXmlSimpleReader reader;
350 	reader.setContentHandler(&handler);
351 	reader.setErrorHandler(&handler);
352 	c->returnValue()->setBoolean(reader.parse(source));
353 #endif
354 	return true;
355 }
356 
KVSO_CLASS_FUNCTION(xmlReader,lastError)357 KVSO_CLASS_FUNCTION(xmlReader, lastError)
358 {
359 	c->returnValue()->setString(m_szLastError);
360 	return true;
361 }
362