You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

xmlgeneric.cc 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*
  2. Common utilities for XML-based formats.
  3. Copyright (C) 2004, 2005, 2006, 2007 Robert Lipe, robertlipe+source@gpsbabel.org
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  15. */
  16. #include "defs.h"
  17. #include "xmlgeneric.h"
  18. #include "cet_util.h"
  19. #include "src/core/file.h"
  20. #include <QtCore/QByteArray>
  21. #include <QtCore/QDebug>
  22. #include <QtCore/QTextCodec>
  23. #include <QtCore/QXmlStreamAttributes>
  24. #include <QtCore/QXmlStreamReader>
  25. #define DEBUG_TAG 0
  26. #if DEBUG_TAG
  27. #include <QtCore/QDebug>
  28. #endif
  29. static QString current_tag;
  30. static xg_tag_mapping* xg_tag_tbl;
  31. static QSet<QString> xg_ignore_taglist;
  32. static QString rd_fname;
  33. static QByteArray reader_data;
  34. static const char* xg_encoding;
  35. static QTextCodec* utf8_codec = QTextCodec::codecForName("UTF-8");
  36. static QTextCodec* codec = utf8_codec; // Qt has no vanilla ASCII encoding =(
  37. #define MYNAME "XML Reader"
  38. /***********************************************************************
  39. * These implement a simple interface for "generic" XML that
  40. * maps reasonably close to 1:1 between XML tags and internal data
  41. * structures.
  42. *
  43. * It doesn't work well for formats (like GPX) that really are "real"
  44. * XML with extended namespaces and such, but it handles many simpler
  45. * xml strains and insulates us from a lot of the grubbiness of expat.
  46. */
  47. xg_callback*
  48. xml_tbl_lookup(const QString& tag, xg_cb_type cb_type)
  49. {
  50. xg_tag_mapping* tm;
  51. for (tm = xg_tag_tbl; tm->tag_cb != NULL; tm++) {
  52. if (str_match(CSTR(tag), tm->tag_name) && (cb_type == tm->cb_type)) {
  53. return tm->tag_cb;
  54. }
  55. }
  56. return NULL;
  57. }
  58. void
  59. xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding)
  60. {
  61. rd_fname = fname;
  62. xg_tag_tbl = tbl;
  63. xg_encoding = encoding;
  64. if (encoding) {
  65. QTextCodec* tcodec = QTextCodec::codecForName(encoding);
  66. if (tcodec) {
  67. codec = tcodec;
  68. }
  69. }
  70. }
  71. void
  72. xml_deinit(void)
  73. {
  74. reader_data.clear();
  75. rd_fname.clear();
  76. xg_tag_tbl = NULL;
  77. xg_encoding = NULL;
  78. codec = utf8_codec;
  79. }
  80. static bool
  81. xml_consider_ignoring(const QStringRef& name)
  82. {
  83. return xg_ignore_taglist.contains(name.toString());
  84. }
  85. static void
  86. xml_run_parser(QXmlStreamReader& reader, QString& current_tag)
  87. {
  88. xg_callback* cb;
  89. while (!reader.atEnd()) {
  90. switch (reader.tokenType()) {
  91. case QXmlStreamReader::StartDocument:
  92. if (!reader.documentEncoding().isEmpty()) {
  93. codec = QTextCodec::codecForName(CSTR(reader.documentEncoding().toString()));
  94. }
  95. if (codec == NULL) {
  96. // According to http://www.opentag.com/xfaq_enc.htm#enc_default , we
  97. // should assume UTF-8 in absense of other informations. Users can
  98. // EASILY override this with xml_init().
  99. codec = QTextCodec::codecForName("UTF-8");
  100. }
  101. break;
  102. case QXmlStreamReader::StartElement:
  103. if (xml_consider_ignoring(reader.name())) {
  104. goto readnext;
  105. }
  106. current_tag.append("/");
  107. current_tag.append(reader.qualifiedName());
  108. cb = xml_tbl_lookup(current_tag, cb_start);
  109. if (cb) {
  110. const QXmlStreamAttributes attrs = reader.attributes();
  111. cb(NULL, &attrs);
  112. }
  113. cb = xml_tbl_lookup(current_tag, cb_cdata);
  114. if (cb) {
  115. QString c = reader.readElementText(QXmlStreamReader::IncludeChildElements);
  116. // readElementText advances the tokenType to QXmlStreamReader::EndElement,
  117. // thus we will not process the EndElement case as we will issue a readNext first.
  118. // does a caller ever expect to be able to use both a cb_cdata and a
  119. // cb_end callback?
  120. cb(c, NULL);
  121. current_tag.chop(reader.qualifiedName().length() + 1);
  122. }
  123. break;
  124. case QXmlStreamReader::EndElement:
  125. if (xml_consider_ignoring(reader.name())) {
  126. goto readnext;
  127. }
  128. cb = xml_tbl_lookup(current_tag, cb_end);
  129. if (cb) {
  130. cb(reader.name().toString(), NULL);
  131. }
  132. current_tag.chop(reader.qualifiedName().length() + 1);
  133. break;
  134. case QXmlStreamReader::Characters:
  135. break;
  136. default:
  137. break;
  138. };
  139. readnext:
  140. // readNextStartElement will cause a "Premature end of document." error
  141. // if you use it to try to read past the end of the document.
  142. // See https://bugreports.qt-project.org/browse/QTBUG-25944,
  143. // which is a bug report from 2012 that doesn't seem to be going anywhere.
  144. reader.readNext();
  145. }
  146. }
  147. void xml_read(void)
  148. {
  149. gpsbabel::File file(rd_fname);
  150. QString current_tag;
  151. file.open(QIODevice::ReadOnly);
  152. QXmlStreamReader reader(&file);
  153. xml_run_parser(reader, current_tag);
  154. if (reader.hasError()) {
  155. fatal(MYNAME ":Read error: %s (%s, line %ld, col %ld)\n",
  156. qPrintable(reader.errorString()),
  157. qPrintable(file.fileName()),
  158. (long) reader.lineNumber(),
  159. (long) reader.columnNumber());
  160. }
  161. }
  162. void xml_ignore_tags(const char** taglist)
  163. {
  164. for (; taglist && *taglist; taglist++) {
  165. xg_ignore_taglist.insert(QString::fromUtf8(*taglist));
  166. }
  167. }
  168. // Chucks some bytes into the global QByteArray buffer and waits for
  169. // xml_readstring() to parse.
  170. void xml_readprefixstring(const char* str)
  171. {
  172. reader_data.append(str);
  173. }
  174. // Parses a bytestream as if it were a file. Looks for an <?xml encoding= to
  175. // determine file encoding, falls back to UTF-8 if unspecified.
  176. void xml_readstring(const char* str)
  177. {
  178. QString current_tag;
  179. reader_data.append(str);
  180. QXmlStreamReader reader(reader_data);
  181. xml_run_parser(reader, current_tag);
  182. if (reader.hasError()) {
  183. fatal(MYNAME ":Read error: %s (%s, line %ld, col %ld)\n",
  184. qPrintable(reader.errorString()),
  185. "unknown",
  186. (long) reader.lineNumber(),
  187. (long) reader.columnNumber());
  188. }
  189. }
  190. // This is quite different from xml_readstring(). It doesn't have to interpret
  191. // encoding because the source is already Qt's internal UTF-16.
  192. void xml_readunicode(const QString& str)
  193. {
  194. QString current_tag;
  195. QXmlStreamReader reader(str);
  196. xml_run_parser(reader, current_tag);
  197. }
  198. /******************************************/