1 /*
2  * This file is part of Office 2007 Filters for Calligra
3  *
4  * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
5  *
6  * Contact: Suresh Chande suresh.chande@nokia.com
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * version 2.1 as published by the Free Software Foundation.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  *
22  */
23 
24 #ifndef MSOOXMLREADER_P_H
25 #define MSOOXMLREADER_P_H
26 
27 #include <klocalizedstring.h>
28 
29 #ifndef MSOOXML_CURRENT_CLASS
30 #error Please include MsooXmlReader_p.h after defining MSOOXML_CURRENT_CLASS and MSOOXML_CURRENT_NS!
31 #endif
32 
33 #define PASTE2(a, b) a##b
34 #define PASTE(a, b) PASTE2( a, b) // indirection needed because only function-like macro parameters can be pasted
35 
36 #define PASTE3_(a, b, c) a##b##c
37 #define PASTE3(a, b, c) PASTE3_( a, b, c) // indirection needed because only function-like macro parameters can be pasted
38 
39 #define JOIN2(a, b) a#b
40 #define JOIN(a, b) JOIN2( a, b) // indirection needed because only function-like macro parameters can be pasted
41 
42 #define STRINGIFY(s) JOIN("", s)
43 
44 //! Used to pass context: creates enum value {el}_{CURRENT_EL}
45 #define PASS_CONTEXT(el) PASTE3(el, _, CURRENT_EL)
46 
47 #define TRY_READ_WITH_ARGS_INTERNAL(name, args, context) \
48     args \
49     RETURN_IF_ERROR( read_ ## name (context) )
50 
51 #define TRY_READ_WITH_ARGS(name, args) \
52     TRY_READ_WITH_ARGS_INTERNAL(name, m_read_ ## name ## _args = args,)
53 
54 #define TRY_READ_WITH_ARGS_IN_CONTEXT(name, args) \
55     TRY_READ_WITH_ARGS_INTERNAL(name, m_read_ ## name ## _args = args, PASS_CONTEXT(name))
56 
57 #define TRY_READ(name) \
58     TRY_READ_WITH_ARGS_INTERNAL(name, ,)
59 
60 #define TRY_READ_IN_CONTEXT(name) \
61     TRY_READ_WITH_ARGS_INTERNAL(name, , PASS_CONTEXT(name))
62 
63 #ifdef MSOOXML_CURRENT_NS
64 # define QUALIFIED_NAME(name) \
65     JOIN(MSOOXML_CURRENT_NS ":",name)
66 #else
67 # define QUALIFIED_NAME(name) \
68     STRINGIFY(name)
69 #endif
70 
71 #ifdef NDEBUG
72 # define PUSH_NAME_INTERNAL
73 # define POP_NAME_INTERNAL
74 #else // DEBUG
75 //! returns caller name at the current scope or "top level"
76 #define CALL_STACK_TOP_NAME (m_callsNamesDebug.isEmpty() \
77     ? QByteArray("top level") : m_callsNamesDebug.top()).constData()
78 # define PUSH_NAME
79 //! put at beginning of each read_*() method on call stack, only in debug mode
80 # define PUSH_NAME_INTERNAL \
81     /*debugMsooXml << CALL_STACK_TOP_NAME << "==>" << QUALIFIED_NAME(CURRENT_EL); */\
82     m_callsNamesDebug.push(STRINGIFY(CURRENT_EL));
83 //! put at the end of each read_*() method on call stack, only in debug mode
84 # define POP_NAME_INTERNAL \
85     m_callsNamesDebug.pop(); \
86     /*debugMsooXml << CALL_STACK_TOP_NAME << "<==" << QUALIFIED_NAME(CURRENT_EL); */
87 #endif
88 
89 #define READ_PROLOGUE2(method) \
90     if (!expectEl(QUALIFIED_NAME(CURRENT_EL))) { \
91         return KoFilter::WrongFormat; \
92     } \
93     PUSH_NAME_INTERNAL \
94 
95 #define READ_PROLOGUE \
96     READ_PROLOGUE2(CURRENT_EL)
97 
98 #define READ_PROLOGUE_IF_NS(ns) \
99     if (!expectEl(JOIN(STRINGIFY(ns) ":",CURRENT_EL))) { \
100         return KoFilter::WrongFormat; \
101     } \
102     PUSH_NAME_INTERNAL \
103 
104 #define READ_EPILOGUE_WITHOUT_RETURN \
105     POP_NAME_INTERNAL \
106     if (!expectElEnd(QUALIFIED_NAME(CURRENT_EL))) { \
107         /*debugMsooXml << "READ_EPILOGUE:" << QUALIFIED_NAME(CURRENT_EL) << "not found!"; */\
108         return KoFilter::WrongFormat; \
109     } \
110     /*debugMsooXml << "/READ_EPILOGUE_WITHOUT_RETURN";*/
111 
112 #define READ_EPILOGUE \
113     /*debugMsooXml << "READ_EPILOGUE";*/ \
114     READ_EPILOGUE_WITHOUT_RETURN \
115     return KoFilter::OK;
116 
117 #define READ_EPILOGUE_IF_NS(ns) \
118     POP_NAME_INTERNAL \
119     if (!expectElEnd((JOIN(STRINGIFY(ns) ":",CURRENT_EL)))) { \
120         return KoFilter::WrongFormat; \
121     } \
122     return KoFilter::OK;
123 
124 #define BREAK_IF_END_OF_QSTRING(name) \
125     /*debugMsooXml << "BREAK_IF_END_OF" << name << "found:" << qualifiedName();*/ \
126     if (isEndElement() && qualifiedName() == name) { \
127         break; \
128     }
129 
130 #define BREAK_IF_END_OF(name) \
131     BREAK_IF_END_OF_QSTRING(QLatin1String(QUALIFIED_NAME(name)))
132 
133 #define BREAK_IF_END_OF_WITH_NS(ns, name) \
134     BREAK_IF_END_OF_QSTRING(QLatin1String(JOIN(STRINGIFY(ns) ":",name)))
135 
136 //inline bool aaaa(const char * aa) { debugMsooXml << "aa" << aa; return true; }
137 
138 #define QUALIFIED_NAME_IS(name) \
139     (qualifiedName() == QLatin1String(QUALIFIED_NAME(name)))
140 
141 #define TRY_READ_IF_INTERNAL(name, qname, context) \
142     if (qualifiedName() == QLatin1String(qname)) { \
143         if (!isStartElement()) { /* sanity check */ \
144             raiseError(i18n("Start element \"%1\" expected, found \"%2\"", \
145                        QLatin1String(STRINGIFY(name)), tokenString())); \
146             return KoFilter::WrongFormat; \
147         } \
148         /*debugMsooXml << "TRY_READ_IF " STRINGIFY(name) " started";*/ \
149         TRY_READ_WITH_ARGS_INTERNAL(name, , context) \
150         /*debugMsooXml << "TRY_READ_IF " STRINGIFY(name) " finished";*/ \
151     }
152 
153 #define TRY_READ_IF_IN_CONTEXT_INTERNAL(name, context) \
154     TRY_READ_IF_INTERNAL(name, QUALIFIED_NAME(name), context)
155 
156 #define TRY_READ_IF_NS_IN_CONTEXT_INTERNAL(ns, name, context) \
157     TRY_READ_IF_INTERNAL(name, JOIN(STRINGIFY(ns) ":", name), context)
158 
159 //! Tries to read element @a name by entering into read_{name} function.
160 //! Must be enclosed within if (isStartElement()), otherwise error will be returned.
161 #define TRY_READ_IF_IN_CONTEXT(name) \
162     TRY_READ_IF_IN_CONTEXT_INTERNAL(name, PASS_CONTEXT(name))
163 
164 #define TRY_READ_IF_NS_IN_CONTEXT(ns, name) \
165     TRY_READ_IF_NS_IN_CONTEXT_INTERNAL(ns, name, PASS_CONTEXT(name))
166 
167 #define TRY_READ_IF(name) \
168     TRY_READ_IF_IN_CONTEXT_INTERNAL(name,)
169 
170 #define ELSE_TRY_READ_IF(name) \
171     else TRY_READ_IF_IN_CONTEXT_INTERNAL(name,)
172 
173 #define ELSE_TRY_READ_IF_IN_CONTEXT(name) \
174     else TRY_READ_IF_IN_CONTEXT_INTERNAL(name, PASS_CONTEXT(name))
175 
176 #define ELSE_TRY_READ_IF_NS_IN_CONTEXT(ns, name) \
177     else TRY_READ_IF_NS_IN_CONTEXT_INTERNAL(ns, name, PASS_CONTEXT(name))
178 
179 #define TRY_READ_IF_NS_INTERNAL(ns, name) \
180     if (qualifiedName() == QLatin1String(JOIN(STRINGIFY(ns) ":", name))) { \
181         /*debugMsooXml << "TRY_READ_IF_NS " JOIN(STRINGIFY(ns) ":", name) " started";*/ \
182         TRY_READ(name); \
183         /*debugMsooXml << "TRY_READ_IF_NS " JOIN(STRINGIFY(ns) ":", name) " finished";*/ \
184     }
185 
186 //! Like TRY_READ_IF() but namespace for explicit namespace @a ns.
187 #define TRY_READ_IF_NS(ns, name) \
188     if (!isStartElement()) { /* sanity check */ \
189         raiseError(i18n("Start element \"%1\" expected, found \"%2\"", QLatin1String(JOIN(STRINGIFY(ns) ":", name)), tokenString())); \
190         return KoFilter::WrongFormat; \
191     } \
192     else TRY_READ_IF_NS_INTERNAL(ns, name)
193 
194 #define ELSE_TRY_READ_IF_NS(ns, name) \
195     else TRY_READ_IF_NS_INTERNAL(ns, name)
196 
197 #define ELSE_WRONG_FORMAT \
198     else { \
199         return KoFilter::WrongFormat; \
200     }
201 
202 #define ELSE_WRONG_FORMAT_DEBUG(dbg) \
203     else { \
204         debugMsooXml << dbg; \
205         return KoFilter::WrongFormat; \
206     }
207 
208 //! Reads optional attribute of name @a atrname and allocates variable of the same name.
209 /*! Requires the following line to be present above:
210     @code
211     const QXmlStreamAttributes attrs( attributes() );
212     @endcode
213 */
214 #define TRY_READ_ATTR(atrname) \
215     QString atrname( attrs.value(QUALIFIED_NAME(atrname)).toString() );
216 
217 //! Reads optional attribute of name @a atrname into the variable @a destination.
218 /*! Requires the following line to be present above:
219     @code
220     const QXmlStreamAttributes attrs( attributes() );
221     @endcode
222 */
223 #define TRY_READ_ATTR_INTO(atrname, destination) \
224     destination = attrs.value(QUALIFIED_NAME(atrname)).toString(); \
225     /*debugMsooXml << "TRY_READ_ATTR_INTO: " STRINGIFY(destination) << "=" << destination;*/
226 
227 //! Reads optional attribute of name @a atrname with explicitly specified namespace @a ns.
228 /*! Creates QString variable with name \<ns\>_\<atrame\>
229 */
230 /*! Requires the following line to be present above:
231     @code
232     const QXmlStreamAttributes attrs( attributes() );
233     @endcode
234 */
235 #define TRY_READ_ATTR_WITH_NS(ns, atrname) \
236     QString PASTE3(ns, _, atrname)( attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString() );
237 
238 //! Reads optional attribute of name @a atrname with explicitly specified namespace @a ns
239 //! into the variable @a destination.
240 /*! Requires the following line to be present above:
241     @code
242     const QXmlStreamAttributes attrs( attributes() );
243     @endcode
244 */
245 #define TRY_READ_ATTR_WITH_NS_INTO(ns, atrname, destination) \
246     destination = attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString(); \
247     /*debugMsooXml << "TRY_READ_ATTR_WITH_NS_INTO: " STRINGIFY(destination) << "=" << destination;*/
248 
atrToString(const QXmlStreamAttributes & attrs,const char * atrname)249 inline QString atrToString(const QXmlStreamAttributes& attrs, const char* atrname)
250 {
251     const QStringRef v(attrs.value(atrname));
252     return v.isNull() ? QString() : v.toString();
253 }
254 
255 //! Reads optional attribute of name @a atrname without namespace.
256 /*! Creates QString variable with name \<atrname\>
257 */
258 /*! Requires the following line to be present above:
259     @code
260     const QXmlStreamAttributes attrs( attributes() );
261     @endcode
262 */
263 #define TRY_READ_ATTR_WITHOUT_NS(atrname) \
264     QString atrname( atrToString(attrs, STRINGIFY(atrname)) );
265 
266 //! Reads required attribute of name @a atrname and allocates variable of the same name
267 //! If there is no such attribute, returns KoFilter::WrongFormat.
268 /*! Requires the following line to be present above:
269     @code
270     const QXmlStreamAttributes attrs( attributes() );
271     @endcode
272 */
273 #define READ_ATTR(atrname) \
274     QString atrname; \
275     if (attrs.hasAttribute(QUALIFIED_NAME(atrname))) { \
276         atrname = attrs.value(QUALIFIED_NAME(atrname)).toString(); \
277     } \
278     ELSE_WRONG_FORMAT_DEBUG( "READ_ATTR: " QUALIFIED_NAME(atrname) " not found" )
279 
280 //! Like @ref READ_ATTR(atrname) but reads the attribute into the variable @a destination.
281 #define READ_ATTR_INTO(atrname, destination) \
282     if (attrs.hasAttribute(QUALIFIED_NAME(atrname))) { \
283         destination = attrs.value(QUALIFIED_NAME(atrname)).toString(); \
284     } \
285     ELSE_WRONG_FORMAT_DEBUG( "READ_ATTR_INTO: " QUALIFIED_NAME(atrname) " not found" )
286 
287 //! Reads required attribute of name @a atrname with explicitly specified namespace @a ns
288 /*! into the variable @a destination.
289 */
290 /*! Requires the following line to be present above:
291     @code
292     const QXmlStreamAttributes attrs( attributes() );
293     @endcode
294 */
295 #define READ_ATTR_WITH_NS_INTO(ns, atrname, destination) \
296     if (attrs.hasAttribute(JOIN(STRINGIFY(ns) ":", atrname))) { \
297         destination = attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString(); \
298     } \
299     ELSE_WRONG_FORMAT_DEBUG( "READ_ATTR_WITH_NS_INTO: " JOIN(STRINGIFY(ns) ":", atrname) " not found" )
300 
301 //! Reads required attribute of name @a atrname with explicitly specified namespace @a ns.
302 /*! Creates QString variable with name \<ns\>_\<atrame\>
303 */
304 /*! Requires the following line to be present above:
305     @code
306     const QXmlStreamAttributes attrs( attributes() );
307     @endcode
308 */
309 #define READ_ATTR_WITH_NS(ns, atrname) \
310     QString PASTE3(ns, _, atrname); \
311     if (attrs.hasAttribute(JOIN(STRINGIFY(ns) ":", atrname))) { \
312         PASTE3(ns, _, atrname) = attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString(); \
313     } \
314     ELSE_WRONG_FORMAT_DEBUG( "READ_ATTR_WITH_NS: " JOIN(STRINGIFY(ns) ":", atrname) " not found" )
315 
316 //! Reads required attribute of name @a atrname without namespace.
317 /*! Creates QString variable with name \<atrname\>
318 */
319 /*! Requires the following line to be present above:
320     @code
321     const QXmlStreamAttributes attrs( attributes() );
322     @endcode
323 */
324 #define READ_ATTR_WITHOUT_NS(atrname) \
325     QString atrname; \
326     if (attrs.hasAttribute(STRINGIFY(atrname))) { \
327         atrname = attrs.value(STRINGIFY(atrname)).toString(); \
328     } \
329     ELSE_WRONG_FORMAT_DEBUG( "READ_ATTR_WITHOUT_NS: " STRINGIFY(atrname) " not found" )
330 
331 //! Like @ref READ_ATTR_WITHOUT_NS(atrname) but reads the attribute into the variable @a destination.
332 #define READ_ATTR_WITHOUT_NS_INTO(atrname, destination) \
333     if (attrs.hasAttribute(STRINGIFY(atrname))) { \
334         destination = attrs.value(STRINGIFY(atrname)).toString(); \
335     } \
336     ELSE_WRONG_FORMAT_DEBUG( "READ_ATTR_WITHOUT_NS_INTO: " STRINGIFY(atrname) " not found" )
337 
338 
339 /*! Requires the following line to be present above:
340     @code
341     const QXmlStreamAttributes attrs( attributes() );
342     @endcode
343 */
344 #define TRY_READ_ATTR_QSTRING(atrname) \
345     QString atrname( attrs.value(m_defaultNamespace + atrname).toString() );
346 
347 //! Like @ref TRY_READ_ATTR_WITHOUT_NS(atrname) but reads the attribute into the variable @a destination.
348 #define TRY_READ_ATTR_WITHOUT_NS_INTO(atrname, destination) \
349     destination = attrs.value(STRINGIFY(atrname)).toString();
350 
351 //! reads boolean attribute "val" prefixed with default namespace, defaults to true
352 #define READ_BOOLEAN_VAL \
353     readBooleanAttr(QUALIFIED_NAME(val), true)
354 
355 //! Converts @a string into integer @a destination; returns KoFilter::WrongFormat on failure.
356 //! @warning @a destination is left unchanged if @a string is empty, so it is up to developer to initialize it.
357 #define STRING_TO_INT(string, destination, debugElement) \
358     if (string.isEmpty()) {} else { \
359         bool ok; \
360         const int val_tmp = string.toInt(&ok); \
361         if (!ok) { \
362             debugMsooXml << "STRING_TO_INT: error converting" << string << "to int (attribute" << debugElement << ")"; \
363             return KoFilter::WrongFormat; \
364         } \
365         destination = val_tmp; \
366     }
367 
368 //! Converts @a string into longlong @a destination; returns KoFilter::WrongFormat on failure.
369 //! @warning @a destination is left unchanged if @a string is empty, so it is up to developer to initialize it.
370 #define STRING_TO_LONGLONG(string, destination, debugElement) \
371     if (string.isEmpty()) {} else { \
372         bool ok; \
373         const quint64 val_tmp = string.toLongLong(&ok); \
374         if (!ok) { \
375             debugMsooXml << "STRING_TO_LONGLONG: error converting" << string << "to LONGLONG (attribute" << debugElement << ")"; \
376             return KoFilter::WrongFormat; \
377         } \
378         destination = val_tmp; \
379     }
380 
381 //! Converts @a string into a qreal value in @a destination; returns KoFilter::WrongFormat on failure.
382 //! @warning @a destination is left unchanged if @a string is empty, so it is up to developer to initialize it.
383 #define STRING_TO_QREAL(string, destination, debugElement) \
384     if (string.isEmpty()) {} else { \
385         bool ok; \
386         const qreal val_tmp = string.toDouble(&ok); \
387         if (!ok) { \
388             debugMsooXml << "STRING_TO_DOUBLE: error converting" << string << "to qreal (attribute" << debugElement << ")"; \
389             return KoFilter::WrongFormat; \
390         } \
391         destination = val_tmp; \
392     }
393 
394 //! Skips everything until end of CURRENT_EL is pulled
395 #define SKIP_EVERYTHING \
396     /*debugMsooXml << "Skipping everything in element" << qualifiedName() << "...";*/ \
397     const QString qn(qualifiedName().toString()); \
398     /*debugMsooXml << *this; */\
399     while (true) { \
400         readNext(); \
401         if (atEnd()) \
402             break; \
403         if (isEndElement() && qualifiedName() == qn) { \
404             break; \
405         } \
406     }
407 
408 //! Skips elements, which cannot be interpreted at this time in order to avoid them
409 // being read somewhere else
410 #define SKIP_UNKNOWN \
411     else { \
412         skipCurrentElement(); \
413     }
414 
415 #define SKIP_EVERYTHING_AND_RETURN \
416     SKIP_EVERYTHING \
417     return KoFilter::OK;
418 
419 #define BIND_READ_METHOD(name, method) \
420     m_readMethods.insert(QLatin1String(name), &MSOOXML_CURRENT_CLASS::read_ ## method);
421 
422 #define BIND_READ(name) \
423     BIND_READ_METHOD(STRINGIFY(name), name)
424 
425 #define BIND_READ_SKIP(name) \
426     BIND_READ_METHOD(STRINGIFY(name), SKIP)
427 
428 #define BIND_READ_METHOD_HASH(hash, name, method) \
429     hash.insert(QLatin1String(name), &MSOOXML_CURRENT_CLASS::read_ ## method);
430 
431 #define BIND_READ_HASH(hash, name) \
432     BIND_READ_METHOD_HASH(hash, STRINGIFY(name), name)
433 
434 #define BIND_READ_HASH_SKIP(hash, name) \
435     BIND_READ_METHOD_HASH(hash, STRINGIFY(name), SKIP)
436 
437 #endif
438