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