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