|
1 /****************************************************************************** |
|
2 * |
|
3 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 * |
|
5 * Permission to use, copy, modify, and distribute this software and its |
|
6 * documentation under the terms of the GNU General Public License is hereby |
|
7 * granted. No representations are made about the suitability of this software |
|
8 * for any purpose. It is provided "as is" without express or implied warranty. |
|
9 * See the GNU General Public License for more details. |
|
10 * |
|
11 */ |
|
12 |
|
13 |
|
14 |
|
15 #include "xmlwriter.h" |
|
16 #include "message.h" |
|
17 #include "xmldita.h" |
|
18 |
|
19 char ILLEGAL_UNICODE_REPLACEMENT = ' '; |
|
20 /******************** XmlStream ********************/ |
|
21 XmlStream::XmlStream(const QString &fileName, const QString &aEncoding, const QString &aStandalone, const QString &doctypeStr) : mFile(fileName), \ |
|
22 mStreamP(0), \ |
|
23 mElemStack(), \ |
|
24 mInElement(false) |
|
25 { |
|
26 mCanIndentList.setAutoDelete(true); |
|
27 mIsOpen = mFile.open(IO_WriteOnly); |
|
28 if (mIsOpen) { |
|
29 mStreamP = new QTextStream(&mFile); |
|
30 if (aEncoding == "UTF-8") { |
|
31 mStreamP->setEncoding(QTextStream::UnicodeUTF8); |
|
32 } else if(aEncoding == "Latin1") { |
|
33 mStreamP->setEncoding(QTextStream::Latin1); |
|
34 } else if(aEncoding == "ISO-8859-1") { |
|
35 mStreamP->setEncoding(QTextStream::Latin1); |
|
36 } else { |
|
37 // No specific encoding set |
|
38 } |
|
39 *mStreamP << "<?xml version='1.0' encoding='" << aEncoding << "' standalone='" << aStandalone << "'?>"; |
|
40 if (doctypeStr.length() > 0) { |
|
41 *mStreamP << "\n<!DOCTYPE " << doctypeStr << ">"; |
|
42 } |
|
43 } else { |
|
44 mStreamP = 0; |
|
45 err("Cannot open file %s for writing!\n", fileName.data()); |
|
46 } |
|
47 // Allow output |
|
48 outputResume(); |
|
49 /// Build text -> unicode map |
|
50 unicodeCharTable.insert("copy", "A9"); |
|
51 unicodeCharTable.insert("trade", "2122"); |
|
52 unicodeCharTable.insert("reg", "AE"); |
|
53 unicodeCharTable.insert("lsquo", "60"); |
|
54 unicodeCharTable.insert("rsquo", "B4"); |
|
55 unicodeCharTable.insert("ldquo", "201C"); |
|
56 unicodeCharTable.insert("rdquo", "201D"); |
|
57 unicodeCharTable.insert("ndash", "2013"); |
|
58 unicodeCharTable.insert("mdash", "2014"); |
|
59 unicodeCharTable.insert("Auml", "C4"); |
|
60 unicodeCharTable.insert("Euml", "CB"); |
|
61 unicodeCharTable.insert("Iuml", "CF"); |
|
62 unicodeCharTable.insert("Ouml", "F6"); |
|
63 unicodeCharTable.insert("Uuml", "FC"); |
|
64 unicodeCharTable.insert("Yuml", "178"); |
|
65 unicodeCharTable.insert("auml", "E4"); |
|
66 unicodeCharTable.insert("euml", "EB"); |
|
67 unicodeCharTable.insert("iuml", "EF"); |
|
68 unicodeCharTable.insert("ouml", "F6"); |
|
69 unicodeCharTable.insert("uuml", "FC"); |
|
70 unicodeCharTable.insert("yuml", "FF"); |
|
71 unicodeCharTable.insert("Aacute", "C1"); |
|
72 unicodeCharTable.insert("Eacute", "C9"); |
|
73 unicodeCharTable.insert("Iacute", "CD"); |
|
74 unicodeCharTable.insert("Oacute", "D3"); |
|
75 unicodeCharTable.insert("Uacute", "DA"); |
|
76 unicodeCharTable.insert("Yacute", "DD"); |
|
77 unicodeCharTable.insert("aacute", "E1"); |
|
78 unicodeCharTable.insert("eacute", "E9"); |
|
79 unicodeCharTable.insert("iacute", "ED"); |
|
80 unicodeCharTable.insert("oacute", "F3"); |
|
81 unicodeCharTable.insert("uacute", "FA"); |
|
82 unicodeCharTable.insert("yacute", "FD"); |
|
83 unicodeCharTable.insert("Agrave", "C0"); |
|
84 unicodeCharTable.insert("Egrave", "C8"); |
|
85 unicodeCharTable.insert("Igrave", "CC"); |
|
86 unicodeCharTable.insert("Ograve", "D2"); |
|
87 unicodeCharTable.insert("Ugrave", "F9"); |
|
88 unicodeCharTable.insert("agrave", "E0"); |
|
89 unicodeCharTable.insert("egrave", "E8"); |
|
90 unicodeCharTable.insert("igrave", "EC"); |
|
91 unicodeCharTable.insert("ograve", "F2"); |
|
92 unicodeCharTable.insert("ugrave", "F9"); |
|
93 //unicodeCharTable.insert("ygrave", ""); In doxygen documentation but code unknown or doesn't exist |
|
94 unicodeCharTable.insert("Acirc", "C2"); |
|
95 unicodeCharTable.insert("Ecirc", "CA"); |
|
96 unicodeCharTable.insert("Icirc", "CE"); |
|
97 unicodeCharTable.insert("Ocirc", "D4"); |
|
98 unicodeCharTable.insert("Ucirc", "DB"); |
|
99 unicodeCharTable.insert("acirc", "E2"); |
|
100 unicodeCharTable.insert("ecirc", "EA"); |
|
101 unicodeCharTable.insert("icirc", "EE"); |
|
102 unicodeCharTable.insert("ocirc", "F4"); |
|
103 unicodeCharTable.insert("ucirc", "FB"); |
|
104 //unicodeCharTable.insert("ycirc", ""); In doxygen documentation but code unknown or doesn't exist |
|
105 unicodeCharTable.insert("Atilde", "C3"); |
|
106 unicodeCharTable.insert("Ntilde", "D1"); |
|
107 unicodeCharTable.insert("Otilde", "D5"); |
|
108 unicodeCharTable.insert("atilde", "E3"); |
|
109 unicodeCharTable.insert("ntilde", "F1"); |
|
110 unicodeCharTable.insert("otilde", "F5"); |
|
111 unicodeCharTable.insert("szlig", "DF"); |
|
112 unicodeCharTable.insert("Ccedil", "C7"); |
|
113 unicodeCharTable.insert("ccedil", "E7"); |
|
114 unicodeCharTable.insert("Aring", "C5"); |
|
115 unicodeCharTable.insert("aring", "E5"); |
|
116 unicodeCharTable.insert("Oslash", "D8"); |
|
117 unicodeCharTable.insert("oslash", "F8"); |
|
118 unicodeCharTable.insert("nbsp", "A0"); |
|
119 unicodeCharTable.insert("AElig", "C6"); |
|
120 unicodeCharTable.insert("aelig", "E6"); |
|
121 |
|
122 } |
|
123 |
|
124 void XmlStream::startElement(const QString& aElemName, const AttributeMap& aAttrs) |
|
125 { |
|
126 if (mStreamP && mIsOpen && mCanWrite) { |
|
127 if (mInElement) { |
|
128 // Close existing element |
|
129 *mStreamP << ">"; |
|
130 } else { |
|
131 mInElement = true; |
|
132 } |
|
133 indent(); |
|
134 // Write element name |
|
135 *mStreamP << "<" << aElemName; |
|
136 // Attributes in sorted order |
|
137 AttributeMapIter it = aAttrs.begin(); |
|
138 while (it != aAttrs.end()){ |
|
139 QString attrVal = encodeText(it.data()); |
|
140 #ifdef DITA_OT_BUG_ATTRIBUTE_VALUE_HACK |
|
141 // DITA Open Toolkit error, it fails to re-encode files properly |
|
142 // Replace "<" with "&lt;" |
|
143 // Replace ">" with "&gt;" |
|
144 int fIdx = 0; |
|
145 QString toFind; |
|
146 QString toReplace; |
|
147 toFind = "<"; |
|
148 toReplace = "&lt;"; |
|
149 fIdx = attrVal.find(toFind, 0); |
|
150 while (fIdx != -1) { |
|
151 attrVal.replace(fIdx, toFind.length(), toReplace); |
|
152 fIdx = attrVal.find(toFind, 0); |
|
153 } |
|
154 toFind = ">"; |
|
155 toReplace = "&gt;"; |
|
156 fIdx = attrVal.find(toFind, 0); |
|
157 while (fIdx != -1) { |
|
158 attrVal.replace(fIdx, toFind.length(), toReplace); |
|
159 fIdx = attrVal.find(toFind, 0); |
|
160 } |
|
161 #endif |
|
162 *mStreamP << " " << it.key() << "=\"" << attrVal << "\""; |
|
163 ++it; |
|
164 } |
|
165 // Update internals |
|
166 mInElement = true; |
|
167 //mCanIndent = true; |
|
168 mElemStack.push(&aElemName); |
|
169 mCanIndentList.append(new bool(true)); |
|
170 } |
|
171 } |
|
172 |
|
173 void XmlStream::characters(const QString& aText) |
|
174 { |
|
175 // If this test was not here then if passed an empty string the stream |
|
176 // will end up writing <Element></Element> rather than <Element/> |
|
177 if (aText.length() > 0) { |
|
178 if (mStreamP && mIsOpen && mCanWrite) { |
|
179 closeElementDeclIfOpen(); |
|
180 *mStreamP << encodeText(aText); |
|
181 } |
|
182 // Don't indent mixed content |
|
183 //mCanIndent = false; |
|
184 setLastIndent(false); |
|
185 } |
|
186 #ifdef DITA_TRACE |
|
187 #ifdef DITA_TRACE_TO_XML |
|
188 // Useful for assertion crashes where otherwise the buffer would be lost |
|
189 flush(*mStreamP); |
|
190 #endif |
|
191 #endif |
|
192 } |
|
193 |
|
194 void XmlStream::characters(char c) |
|
195 { |
|
196 if (mStreamP && mIsOpen && mCanWrite) { |
|
197 closeElementDeclIfOpen(); |
|
198 if (isLegalXmlChar(c)) { |
|
199 if (mustEncodeChar(c)) { |
|
200 *mStreamP << encodeChar(c); |
|
201 } else { |
|
202 *mStreamP << c; |
|
203 } |
|
204 } else { |
|
205 *mStreamP << ILLEGAL_UNICODE_REPLACEMENT; |
|
206 } |
|
207 } |
|
208 // Don't indent mixed content |
|
209 //mCanIndent = false; |
|
210 setLastIndent(false); |
|
211 #ifdef DITA_TRACE |
|
212 #ifdef DITA_TRACE_TO_XML |
|
213 // Useful for assertion crashes where otherwise the buffer would be lost |
|
214 flush(*mStreamP); |
|
215 #endif |
|
216 #endif |
|
217 } |
|
218 |
|
219 XmlStream& XmlStream::operator<<(const QCString& s) |
|
220 { |
|
221 characters(s); |
|
222 return *this; |
|
223 } |
|
224 |
|
225 XmlStream& XmlStream::operator<<(const char* s) |
|
226 { |
|
227 characters(s); |
|
228 return *this; |
|
229 } |
|
230 |
|
231 XmlStream& XmlStream::operator<<(char c) |
|
232 { |
|
233 characters(c); |
|
234 return *this; |
|
235 } |
|
236 |
|
237 XmlStream& XmlStream::writeUnicode(const QCString& s) |
|
238 { |
|
239 if (mStreamP && mIsOpen && mCanWrite) { |
|
240 closeElementDeclIfOpen(); |
|
241 if (unicodeCharTable.find(s)) { |
|
242 *mStreamP << "&#x"; |
|
243 *mStreamP << unicodeCharTable[s]; |
|
244 *mStreamP << ";"; |
|
245 } else { |
|
246 // Write a warning as a comment |
|
247 QString cmtTxt = "Can not write Unicode for Doxygen interpreted symbol: \""; |
|
248 cmtTxt += s; |
|
249 cmtTxt += "\""; |
|
250 comment(cmtTxt); |
|
251 } |
|
252 } |
|
253 // Don't indent mixed content |
|
254 //mCanIndent = false; |
|
255 setLastIndent(false); |
|
256 return *this; |
|
257 } |
|
258 |
|
259 void XmlStream::processingInstruction(const QString& aText) |
|
260 { |
|
261 if (mStreamP && mIsOpen && mCanWrite) { |
|
262 closeElementDeclIfOpen(); |
|
263 *mStreamP << "<?" << aText << "?>"; |
|
264 } |
|
265 //mCanIndent = true; |
|
266 } |
|
267 |
|
268 void XmlStream::comment(const QString& aText) |
|
269 { |
|
270 if (mStreamP && mIsOpen && mCanWrite) { |
|
271 closeElementDeclIfOpen(); |
|
272 *mStreamP << "<!-- " << aText << " -->"; |
|
273 } |
|
274 //mCanIndent = true; |
|
275 } |
|
276 |
|
277 void XmlStream::endElement(const QString& aElemName) |
|
278 { |
|
279 if (mStreamP && mIsOpen && mCanWrite) { |
|
280 if (mInElement) { |
|
281 // Use minimal form |
|
282 *mStreamP << "/>"; |
|
283 mInElement = false; |
|
284 mElemStack.pop(); |
|
285 } else { |
|
286 indent(1); |
|
287 *mStreamP << "</" << *(mElemStack.pop()) << ">"; |
|
288 } |
|
289 } |
|
290 mCanIndentList.removeLast(); |
|
291 } |
|
292 |
|
293 void XmlStream::closeElementDeclIfOpen() |
|
294 { |
|
295 if (mStreamP && mIsOpen && mCanWrite) { |
|
296 if (mInElement) { |
|
297 *mStreamP << ">"; |
|
298 mInElement = false; |
|
299 } |
|
300 } |
|
301 } |
|
302 |
|
303 void XmlStream::indent(unsigned int aInitVal) |
|
304 { |
|
305 if (mStreamP && mIsOpen && canIndent() && mCanWrite) { |
|
306 *mStreamP << XML_OUTPUT_ENDL; |
|
307 for (unsigned int i = aInitVal; i < mElemStack.count(); i++) { |
|
308 *mStreamP << XML_INDENT; |
|
309 } |
|
310 } |
|
311 } |
|
312 |
|
313 /** Returns 1 if the character is in the legal unicode range |
|
314 See: http://www.w3.org/TR/REC-xml/#charsets |
|
315 */ |
|
316 inline bool XmlStream::isLegalXmlChar(QChar c) const |
|
317 { |
|
318 ushort u = c.unicode(); |
|
319 //printf("XmlStream::isLegalXmlChar() testing 0x%X\n", u); |
|
320 // This is what the code should be: |
|
321 /* |
|
322 bool result = (u == 0x09 || u == 0x0A || u == 0x0D || \ |
|
323 ((u >= 0x20) && (u <= 0xD7FF)) || \ |
|
324 ((u >= 0xE000) && (u <= 0xFFFD)) \ |
|
325 ); |
|
326 */ |
|
327 // An this is the kludge that prevents weird characters (e.g. 0xA0) |
|
328 // that appear in the source code from getting into the XML. |
|
329 bool result = (u == 0x09 || u == 0x0A || u == 0x0D || \ |
|
330 ((u >= 0x20) && (u <= 0x7F)) \ |
|
331 ); |
|
332 if (!result) { |
|
333 msg("XmlStream::isLegalXmlChar() rejecting 0x%X\n", u); |
|
334 } |
|
335 return result; |
|
336 } |
|
337 |
|
338 QString XmlStream::encodeText(const QString& aStr) const |
|
339 { |
|
340 QCString result; |
|
341 //printf("XmlStream::encodeText() encoding \"%s\"\n", aStr.data()); |
|
342 for (unsigned int i=0; i < aStr.length(); ++i) { |
|
343 if (isLegalXmlChar(aStr[i])) { |
|
344 char c = aStr[i]; |
|
345 if (mustEncodeChar(c)) { |
|
346 result += encodeChar(c); |
|
347 } else { |
|
348 result += c; |
|
349 } |
|
350 } else { |
|
351 result += ILLEGAL_UNICODE_REPLACEMENT; |
|
352 } |
|
353 } |
|
354 return result; |
|
355 } |
|
356 |
|
357 /** Converts a char to a QString using XML entity transformaiton */ |
|
358 QString XmlStream::encodeChar(char c) const |
|
359 { |
|
360 switch (c) { |
|
361 case '<': return QString("<"); break; |
|
362 case '>': return QString(">"); break; |
|
363 case '&': return QString("&"); break; |
|
364 case '\'': return QString("'"); break; |
|
365 case '"': return QString("""); break; |
|
366 default: |
|
367 QString s; |
|
368 s += c; |
|
369 return s; |
|
370 break; |
|
371 } |
|
372 } |
|
373 |
|
374 /** Returns true if a char needs to be converted using XML entity transformaiton */ |
|
375 bool XmlStream::mustEncodeChar(char c) const |
|
376 { |
|
377 switch (c) { |
|
378 // Note fall through |
|
379 case '<': |
|
380 case '>': |
|
381 case '&': |
|
382 case '\'': |
|
383 case '"': |
|
384 return true; |
|
385 break; |
|
386 default: |
|
387 break; |
|
388 } |
|
389 return false; |
|
390 } |
|
391 |
|
392 bool XmlStream::canIndent() |
|
393 { |
|
394 bool *bP; |
|
395 for (bP = mCanIndentList.first(); bP != 0; bP = mCanIndentList.next()) { |
|
396 if (!*bP) { |
|
397 return false; |
|
398 } |
|
399 } |
|
400 return true; |
|
401 } |
|
402 |
|
403 void XmlStream::setLastIndent(bool theB) |
|
404 { |
|
405 mCanIndentList.removeLast(); |
|
406 mCanIndentList.append(new bool(theB)); |
|
407 } |
|
408 |
|
409 /// Suspend output |
|
410 void XmlStream::outputSuspend() |
|
411 { |
|
412 mCanWrite = false; |
|
413 } |
|
414 |
|
415 /// Resume output |
|
416 void XmlStream::outputResume() |
|
417 { |
|
418 mCanWrite = true; |
|
419 } |
|
420 |
|
421 void XmlStream::close() |
|
422 { |
|
423 if (mStreamP) { |
|
424 // Ignore mCanWrite |
|
425 outputResume(); |
|
426 closeElementDeclIfOpen(); |
|
427 while(mElemStack.count()){ |
|
428 endElement(mElemStack[mElemStack.count()-1]); |
|
429 } |
|
430 // Delete the stream and close the file |
|
431 delete mStreamP; |
|
432 mStreamP = 0; |
|
433 mFile.close(); |
|
434 mIsOpen = false; |
|
435 } |
|
436 } |
|
437 |
|
438 |
|
439 XmlStream::~XmlStream() |
|
440 { |
|
441 try { |
|
442 close(); |
|
443 } |
|
444 catch(...) {} |
|
445 } |
|
446 /******************** END: XmlStream ********************/ |
|
447 |
|
448 /******************** XmlElement ********************/ |
|
449 XmlElement::XmlElement(XmlStream& aStream, const QString& aElemName) : mStream(aStream), mElemName(aElemName) |
|
450 { |
|
451 AttributeMap attrs; |
|
452 mStream.startElement(mElemName, attrs); |
|
453 } |
|
454 |
|
455 XmlElement::XmlElement(XmlStream& aStream, const QString& aElemName, const QString& aAttr, const QString& aAttrValue) : mStream(aStream), mElemName(aElemName) |
|
456 { |
|
457 AttributeMap attrs; |
|
458 attrs[aAttr] = aAttrValue; |
|
459 mStream.startElement(mElemName, attrs); |
|
460 } |
|
461 |
|
462 XmlElement::XmlElement(XmlStream& aStream, const QString& aElemName, const QString& aAttr, char aAttrValue) : mStream(aStream), mElemName(aElemName) |
|
463 { |
|
464 AttributeMap attrs; |
|
465 attrs[aAttr] = QChar(aAttrValue); |
|
466 mStream.startElement(mElemName, attrs); |
|
467 } |
|
468 |
|
469 XmlElement::XmlElement(XmlStream& aStream, const QString& aElemName, AttributeMap& aAttrs) : mStream(aStream), mElemName(aElemName) |
|
470 { |
|
471 mStream.startElement(mElemName, aAttrs); |
|
472 } |
|
473 |
|
474 /* |
|
475 // Parse an attribute string of the form "attr_0=value_0 attr_1=value_1" |
|
476 XmlElement::XmlElement(XmlStream& aStream, const QString& aElemName, const QString& aAttrString) : mStream(aStream), mElemName(aElemName) |
|
477 { |
|
478 AttributeMap attrMap; |
|
479 QString myStr = aAttrString.simplifyWhiteSpace(); |
|
480 int s = 0; // Index of start of attr |
|
481 int e = 0; // Index of '=' |
|
482 int v = 0; // Index of end of value |
|
483 while (s < (int) myStr.length()) { |
|
484 e = s; |
|
485 v = s; |
|
486 e = myStr.find('=', s); |
|
487 if (e == -1) { |
|
488 break; |
|
489 } |
|
490 v = myStr.find(' ', s); |
|
491 if (v == -1) { |
|
492 v = myStr.length(); |
|
493 } |
|
494 attrMap[myStr.mid(s, e-s)] = myStr.mid(e+1, v-(e+1)); |
|
495 s = v+1; |
|
496 } |
|
497 mStream.startElement(mElemName, attrMap); |
|
498 } |
|
499 */ |
|
500 |
|
501 XmlElement::~XmlElement() |
|
502 { |
|
503 try { |
|
504 mStream.endElement(mElemName); |
|
505 } |
|
506 catch(...) {} |
|
507 } |
|
508 /******************** END: XmlElement ********************/ |
|
509 |
|
510 /******************** XmlElementStack ********************/ |
|
511 XmlElementStack::XmlElementStack(XmlStream& aStream) : mStream(aStream) |
|
512 { |
|
513 } |
|
514 |
|
515 void XmlElementStack::push(const QString& aElementName) |
|
516 { |
|
517 mElemStack.push(new XmlElement(mStream, aElementName)); |
|
518 } |
|
519 |
|
520 void XmlElementStack::push(const QString& aElementName, const QString& aAttr, const QString& aAttrValue) |
|
521 { |
|
522 mElemStack.push(new XmlElement(mStream, aElementName, aAttr, aAttrValue)); |
|
523 } |
|
524 |
|
525 void XmlElementStack::push(const QString& aElementName, AttributeMap& aAttrs) |
|
526 { |
|
527 mElemStack.push(new XmlElement(mStream, aElementName, aAttrs)); |
|
528 } |
|
529 |
|
530 void XmlElementStack::pop(const QString &aElementName) |
|
531 { |
|
532 XmlElement *pElem = mElemStack.pop(); |
|
533 if (pElem->getElemName() != aElementName) { |
|
534 err(pElem->getElemName() + " is not equal to " + aElementName +"\n"); |
|
535 } |
|
536 ASSERT(pElem->getElemName() == aElementName); |
|
537 delete pElem; |
|
538 } |
|
539 |
|
540 void XmlElementStack::pop() |
|
541 { |
|
542 XmlElement *pElem = mElemStack.pop(); |
|
543 delete pElem; |
|
544 } |
|
545 |
|
546 void XmlElementStack::pushpop(const QString &aElementName) |
|
547 { |
|
548 mElemStack.push(new XmlElement(mStream, aElementName)); |
|
549 pop(aElementName); |
|
550 } |
|
551 |
|
552 void XmlElementStack::pushpop(const QString &aElementName, const QString& aText) |
|
553 { |
|
554 mElemStack.push(new XmlElement(mStream, aElementName)); |
|
555 mStream.characters(aText); |
|
556 pop(aElementName); |
|
557 } |
|
558 |
|
559 bool XmlElementStack::isEmpty() const |
|
560 { |
|
561 return mElemStack.isEmpty(); |
|
562 } |
|
563 |
|
564 const XmlElement& XmlElementStack::peek() const |
|
565 { |
|
566 return *mElemStack.top(); |
|
567 } |
|
568 |
|
569 void XmlElementStack::addAttribute(const QString &name, const QString &value) |
|
570 { |
|
571 XmlElement *pElem = mElemStack.pop(); |
|
572 QString elemenName = pElem->getElemName(); |
|
573 delete pElem; |
|
574 XmlElement *elem = new XmlElement(mStream, elemenName, name, value); |
|
575 mElemStack.push(elem); |
|
576 } |
|
577 |
|
578 void XmlElementStack::close() |
|
579 { |
|
580 while(mElemStack.count()){ |
|
581 pop(); |
|
582 } |
|
583 } |
|
584 |
|
585 XmlElementStack::~XmlElementStack() |
|
586 { |
|
587 try { |
|
588 close(); |
|
589 } |
|
590 catch(...) {} |
|
591 } |
|
592 /******************** END: XmlElementStack ********************/ |
|
593 |