openMSX
rapidsax.hh
Go to the documentation of this file.
1 #ifndef RAPIDSAX_HH
2 #define RAPIDSAX_HH
3 
4 // This code is _heavily_ based on RapidXml 1.13
5 // http://rapidxml.sourceforge.net/
6 //
7 // RapidXml is a very fast XML parser.
8 // http://xmlbench.sourceforge.net/results/benchmark200910/index.html
9 // One of the main reasons it can be this fast is that doesn't do any string
10 // copies. Instead the XML input data is modified in-place (e.g. for stuff like
11 // < replacements). Though this also means the output produced by the parser
12 // is tied to the lifetime of the XML input data.
13 //
14 // RapidXml produces a DOM-like output. This parser has a SAX-like interface.
15 
16 #include "one_of.hh"
17 #include "small_compare.hh"
18 #include <cassert>
19 #include <cstdint>
20 #include <string_view>
21 
22 namespace rapidsax {
23 
24 // Parse given XML text and call callback functions in the given handler.
25 // - XML text must be zero-terminated
26 // - Handler must implement the methods defined in NullHandler (below). An
27 // easy way to do this is to inherit from NullHandler and only reimplement
28 // the methods that you need.
29 // - The behavior of the parser can be fine-tuned with the FLAGS parameter,
30 // see below for more details.
31 // - When a parse error is encounter, an instance of ParseError is thrown.
32 // - The lifetime of the string_view's in the callback handler is the same as
33 // the lifetime of the input XML data (no string copies are made, instead
34 // the XML file is modified in-place and references to this data are passed).
35 template<int FLAGS, typename HANDLER> void parse(HANDLER& handler, char* xml);
36 
37 // When loading an XML file from disk, the buffer needs to be 8 bytes bigger
38 // than the filesize. The first of these bytes must be filled with zero
39 // (zero-terminate the xml data). The other bytes are only there to allow to
40 // read up-to 8 bytes past the end without triggering memory protection errors.
41 constexpr size_t EXTRA_BUFFER_SPACE = 8;
42 
43 
44 // Flags that influence parsing behavior. The flags can be OR'ed together.
45 
46 // Should XML entities like &lt; be expanded or not?
47 constexpr int noEntityTranslation = 0x1;
48 // Should leading and trailing whitespace be trimmed?
49 constexpr int trimWhitespace = 0x2;
50 // Should sequences of whitespace characters be replaced with a single
51 // space character?
52 constexpr int normalizeWhitespace = 0x4;
53 // Should strings be modified (in-place) with a zero-terminator?
54 constexpr int zeroTerminateStrings = 0x8;
55 
56 
57 // Callback handler with all empty implementations (can be used as a base
58 // class in case you only need to reimplement a few of the methods).
60 {
61 public:
62  // Called when an opening XML tag is encountered.
63  // 'name' is the name of the XML tag.
64  void start(std::string_view /*name*/) {}
65 
66  // Called when a XML tag is closed.
67  // Note: the parser does currently not check whether the name of the
68  // opening nd closing tags matches.
69  void stop() {}
70 
71  // Called when text inside a tag is parsed.
72  // XML entities are replaced (optional)
73  // Whitespace is (optionally) trimmed or normalized.
74  // This method is not called for an empty text string.
75  // (Unlike other SAX parsers) the whole text string is always
76  // passed in a single chunk (so no need to concatenate this text
77  // with previous chunks in the callback).
78  void text(std::string_view /*text*/) {}
79 
80  // Called for each parsed attribute.
81  // Attributes can occur inside xml tags or inside XML declarations.
82  void attribute(std::string_view /*name*/, std::string_view /*value*/) {}
83 
84  // Called for parsed CDATA sections.
85  void cdata(std::string_view /*value*/) {}
86 
87  // Called when a XML comment (<!-- ... -->) is parsed.
88  void comment(std::string_view /*value*/) {}
89 
90  // Called when XML declaration (<?xml .. ?>) is parsed.
91  // Inside a XML declaration there can be attributes.
92  void declarationStart() {}
93  void declAttribute(std::string_view /*name*/, std::string_view /*value*/) {}
94  void declarationStop() {}
95 
96  // Called when the <!DOCTYPE ..> is parsed.
97  void doctype(std::string_view /*text*/) {}
98 
99  // Called when XML processing instructions (<? .. ?>) are parsed.
100  void procInstr(std::string_view /*target*/, std::string_view /*instr*/) {}
101 };
102 
103 
105 {
106 public:
107  ParseError(const char* what_, char* where_)
108  : m_what(what_)
109  , m_where(where_)
110  {
111  }
112 
113  [[nodiscard]] const char* what() const { return m_what; }
114  [[nodiscard]] char* where() const { return m_where; }
115 
116 private:
117  const char* m_what;
118  char* m_where;
119 };
120 
121 
122 namespace internal {
123 
124 extern const uint8_t lutChar [256]; // Character class
125 extern const uint8_t lutDigits[256]; // Digits
126 
127 // Detect whitespace character (space \n \r \t)
129  [[nodiscard]] static bool test(char ch) { return (lutChar[uint8_t(ch)] & 0x02) != 0; }
130 };
131 
132 // Detect node name character (anything but space \n \r \t / > ? \0)
133 struct NodeNamePred {
134  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x43); }
135 };
136 
137 // Detect attribute name character (anything but space \n \r \t / < > = ? ! \0)
139  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0xC7); }
140 };
141 
142 // Detect text character (PCDATA) (anything but < \0)
143 struct TextPred {
144  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x05); }
145 };
146 
147 // Detect text character (PCDATA) that does not require processing when ws
148 // normalization is disabled (anything but < \0 &)
150  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x0D); }
151 };
152 
153 // Detect text character (PCDATA) that does not require processing when ws
154 // normalization is enabled (anything but < \0 & space \n \r \t)
156  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x0F); }
157 };
158 
159 // Detect attribute value character, single quote (anything but ' \0)
160 struct AttPred1 {
161  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x11); }
162 };
163 // Detect attribute value character, double quote (anything but " \0)
164 struct AttPred2 {
165  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x21); }
166 };
167 
168 // Detect attribute value character, single quote, that does not require
169 // processing (anything but ' \0 &)
170 struct AttPurePred1 {
171  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x19); }
172 };
173 // Detect attribute value character, double quote, that does not require
174 // processing (anything but " \0 &)
175 struct AttPurePred2 {
176  [[nodiscard]] static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x29); }
177 };
178 
179 // Insert coded character, using UTF8
180 inline void insertUTF8char(char*& text, uint32_t code)
181 {
182  if (code < 0x80) { // 1 byte sequence
183  text[0] = char(code);
184  text += 1;
185  } else if (code < 0x800) {// 2 byte sequence
186  text[1] = char((code | 0x80) & 0xBF); code >>= 6;
187  text[0] = char (code | 0xC0);
188  text += 2;
189  } else if (code < 0x10000) { // 3 byte sequence
190  text[2] = char((code | 0x80) & 0xBF); code >>= 6;
191  text[1] = char((code | 0x80) & 0xBF); code >>= 6;
192  text[0] = char (code | 0xE0);
193  text += 3;
194  } else if (code < 0x110000) { // 4 byte sequence
195  text[3] = char((code | 0x80) & 0xBF); code >>= 6;
196  text[2] = char((code | 0x80) & 0xBF); code >>= 6;
197  text[1] = char((code | 0x80) & 0xBF); code >>= 6;
198  text[0] = char (code | 0xF0);
199  text += 4;
200  } else { // Invalid, only codes up to 0x10FFFF are allowed in Unicode
201  throw ParseError("invalid numeric character entity", text);
202  }
203 }
204 
205 template<StringLiteral Str> [[nodiscard]] static inline bool next(const char* p)
206 {
207  return small_compare<Str>(p);
208 }
209 
210 
211 // Skip characters until predicate evaluates to true
212 template<typename StopPred> static inline void skip(char*& text)
213 {
214  char* tmp = text;
215  while (StopPred::test(*tmp)) ++tmp;
216  text = tmp;
217 }
218 
219 // Skip characters until predicate evaluates to true while doing the following:
220 // - replacing XML character entity references with proper characters
221 // (&apos; &amp; &quot; &lt; &gt; &#...;)
222 // - condensing whitespace sequences to single space character
223 template<typename StopPred, class StopPredPure, int FLAGS>
224 [[nodiscard]] static inline char* skipAndExpand(char*& text)
225 {
226  // If entity translation, whitespace condense and whitespace
227  // trimming is disabled, use plain skip.
228  if constexpr ( (FLAGS & noEntityTranslation) &&
229  !(FLAGS & normalizeWhitespace) &&
230  !(FLAGS & trimWhitespace)) {
231  skip<StopPred>(text);
232  return text;
233  }
234 
235  // Use simple skip until first modification is detected
236  skip<StopPredPure>(text);
237 
238  // Use translation skip
239  char* src = text;
240  char* dest = src;
241  while (StopPred::test(*src)) {
242  // Test if replacement is needed
243  if (!(FLAGS & noEntityTranslation) &&
244  (src[0] == '&')) {
245  switch (src[1]) {
246  case 'a': // &amp; &apos;
247  if (next<"amp;">(&src[1])) {
248  *dest = '&';
249  ++dest;
250  src += 5;
251  continue;
252  }
253  if (next<"pos;">(&src[2])) {
254  *dest = '\'';
255  ++dest;
256  src += 6;
257  continue;
258  }
259  break;
260 
261  case 'q': // &quot;
262  if (next<"uot;">(&src[2])) {
263  *dest = '"';
264  ++dest;
265  src += 6;
266  continue;
267  }
268  break;
269 
270  case 'g': // &gt;
271  if (next<"t;">(&src[2])) {
272  *dest = '>';
273  ++dest;
274  src += 4;
275  continue;
276  }
277  break;
278 
279  case 'l': // &lt;
280  if (next<"t;">(&src[2])) {
281  *dest = '<';
282  ++dest;
283  src += 4;
284  continue;
285  }
286  break;
287 
288  case '#': // &#...; - assumes ASCII
289  if (src[2] == 'x') {
290  uint32_t code = 0;
291  src += 3; // skip &#x
292  while (true) {
293  uint8_t digit = lutDigits[uint8_t(*src)];
294  if (digit == 0xFF) break;
295  code = code * 16 + digit;
296  ++src;
297  }
298  insertUTF8char(dest, code);
299  } else {
300  uint32_t code = 0;
301  src += 2; // skip &#
302  while (true) {
303  uint8_t digit = lutDigits[uint8_t(*src)];
304  if (digit == 0xFF) break;
305  code = code * 10 + digit;
306  ++src;
307  }
308  insertUTF8char(dest, code);
309  }
310  if (*src != ';') {
311  throw ParseError("expected ;", src);
312  }
313  ++src;
314  continue;
315 
316  default:
317  // Something else, ignore, just copy '&' verbatim
318  break;
319  }
320  }
321 
322  // Test if condensing is needed
323  if ((FLAGS & normalizeWhitespace) &&
324  (WhitespacePred::test(*src))) {
325  *dest++ = ' '; // single space in dest
326  ++src; // skip first whitespace char
327  // Skip remaining whitespace chars
328  while (WhitespacePred::test(*src)) ++src;
329  continue;
330  }
331 
332  // No replacement, only copy character
333  *dest++ = *src++;
334  }
335 
336  // Return new end
337  text = src;
338  return dest;
339 }
340 
341 inline void skipBOM(char*& text)
342 {
343  if (next<"\357\273\277">(text)) { // char(0xEF), char(0xBB), char(0xBF)
344  text += 3; // skip utf-8 bom
345  }
346 }
347 
348 
349 template<int FLAGS, typename HANDLER> class Parser
350 {
351  HANDLER& handler;
352 
353 public:
354  Parser(HANDLER& handler_, char* text)
355  : handler(handler_)
356  {
357  skipBOM(text);
358  while (true) {
359  // Skip whitespace before node
360  skip<WhitespacePred>(text);
361  if (*text == 0) break;
362 
363  if (*text != '<') {
364  throw ParseError("expected <", text);
365  }
366  ++text; // skip '<'
367  parseNode(text);
368  }
369  }
370 
371 private:
372  // Parse XML declaration (<?xml...)
373  void parseDeclaration(char*& text)
374  {
375  handler.declarationStart();
376  skip<WhitespacePred>(text); // skip ws before attributes or ?>
377  parseAttributes(text, true);
378  handler.declarationStop();
379 
380  // skip ?>
381  if (!next<"?>">(text)) {
382  throw ParseError("expected ?>", text);
383  }
384  text += 2;
385  }
386 
387  // Parse XML comment (<!--...)
388  void parseComment(char*& text)
389  {
390  // Skip until end of comment
391  char* value = text; // remember value start
392  while (!next<"-->">(text)) {
393  if (text[0] == 0) {
394  throw ParseError("unexpected end of data", text);
395  }
396  ++text;
397  }
398  if (FLAGS & zeroTerminateStrings) {
399  *text = '\0';
400  }
401  handler.comment(std::string_view(value, text - value));
402  text += 3; // skip '-->'
403  }
404 
405  void parseDoctype(char*& text)
406  {
407  char* value = text; // remember value start
408 
409  // skip to >
410  while (*text != '>') {
411  switch (*text) {
412  case '[': {
413  // If '[' encountered, scan for matching ending
414  // ']' using naive algorithm with depth. This
415  // works for all W3C test files except for 2
416  // most wicked.
417  ++text; // skip '['
418  int depth = 1;
419  while (depth > 0) {
420  switch (*text) {
421  case char('['): ++depth; break;
422  case char(']'): --depth; break;
423  case 0: throw ParseError(
424  "unexpected end of data", text);
425  }
426  ++text;
427  }
428  break;
429  }
430  case '\0':
431  throw ParseError("unexpected end of data", text);
432 
433  default:
434  ++text;
435  }
436  }
437 
438  if (FLAGS & zeroTerminateStrings) {
439  *text = '\0';
440  }
441  handler.doctype(std::string_view(value, text - value));
442  text += 1; // skip '>'
443  }
444 
445  void parsePI(char*& text)
446  {
447  // Extract PI target name
448  char* name = text;
449  skip<NodeNamePred>(text);
450  char* nameEnd = text;
451  if (name == nameEnd) {
452  throw ParseError("expected PI target", text);
453  }
454 
455  // Skip whitespace between pi target and pi
456  skip<WhitespacePred>(text);
457 
458  // Skip to '?>'
459  char* value = text; // Remember start of pi
460  while (!next<"?>">(text)) {
461  if (*text == 0) {
462  throw ParseError("unexpected end of data", text);
463  }
464  ++text;
465  }
466  // Set pi value (verbatim, no entity expansion or ws normalization)
467  if (FLAGS & zeroTerminateStrings) {
468  *nameEnd = '\0';
469  *text = '\0';
470  }
471  handler.procInstr(std::string_view(name, nameEnd - name),
472  std::string_view(value, text - value));
473  text += 2; // skip '?>'
474  }
475 
476  void parseText(char*& text, char* contentsStart)
477  {
478  // Backup to contents start if whitespace trimming is disabled
479  if constexpr (!(FLAGS & trimWhitespace)) {
480  text = contentsStart;
481  }
482  // Skip until end of data
483  char* value = text;
484  char* end = (FLAGS & normalizeWhitespace)
485  ? skipAndExpand<TextPred, TextPureWithWsPred, FLAGS>(text)
486  : skipAndExpand<TextPred, TextPureNoWsPred , FLAGS>(text);
487 
488  // Trim trailing whitespace; leading was already trimmed by
489  // whitespace skip after >
490  if constexpr ((FLAGS & trimWhitespace) != 0) {
491  if constexpr (FLAGS & normalizeWhitespace) {
492  // Whitespace is already condensed to single
493  // space characters by skipping function, so
494  // just trim 1 char off the end.
495  if (end[-1] == ' ') {
496  --end;
497  }
498  } else {
499  // Backup until non-whitespace character is found
500  while (WhitespacePred::test(end[-1])) {
501  --end;
502  }
503  }
504  }
505 
506  // check next char before calling handler.text()
507  if (*text == '\0') {
508  throw ParseError("unexpected end of data", text);
509  } else {
510  assert(*text == '<');
511  }
512 
513  // Handle text, but only if non-empty.
514  auto len = end - value;
515  if (len) {
516  if (FLAGS & zeroTerminateStrings) {
517  *text = '\0';
518  }
519  handler.text(std::string_view(value, len));
520  }
521  }
522 
523  void parseCdata(char*& text)
524  {
525  // Skip until end of cdata
526  char* value = text;
527  while (!next<"]]>">(text)) {
528  if (text[0] == 0) {
529  throw ParseError("unexpected end of data", text);
530  }
531  ++text;
532  }
533  if (FLAGS & zeroTerminateStrings) {
534  *text = '\0';
535  }
536  handler.cdata(std::string_view(value, text - value));
537  text += 3; // skip ]]>
538  }
539 
540  void parseElement(char*& text)
541  {
542  // Extract element name
543  char* name = text;
544  skip<NodeNamePred>(text);
545  char* nameEnd = text;
546  if (name == nameEnd) {
547  throw ParseError("expected element name", text);
548  }
549  handler.start(std::string_view(name, nameEnd - name));
550 
551  skip<WhitespacePred>(text); // skip ws before attributes or >
552  parseAttributes(text, false);
553 
554  // Determine ending type
555  if (*text == '>') {
556  if (FLAGS & zeroTerminateStrings) {
557  *nameEnd = '\0';
558  }
559  ++text;
560  parseNodeContents(text);
561  } else if (*text == '/') {
562  if (FLAGS & zeroTerminateStrings) {
563  *nameEnd = '\0';
564  }
565  handler.stop();
566  ++text;
567  if (*text != '>') {
568  throw ParseError("expected >", text);
569  }
570  ++text;
571  } else {
572  throw ParseError("expected >", text);
573  }
574  }
575 
576  // Determine node type, and parse it
577  void parseNode(char*& text)
578  {
579  switch (text[0]) {
580  case '?': // <?...
581  ++text; // skip ?
582  // Note: this doesn't detect mixed case (xMl), does
583  // that matter?
584  if ((next<"xml">(text) || next<"XML">(text)) &&
585  WhitespacePred::test(text[3])) {
586  // '<?xml ' - xml declaration
587  text += 4; // skip 'xml '
588  parseDeclaration(text);
589  } else {
590  parsePI(text);
591  }
592  break;
593 
594  case '!': // <!...
595  // Parse proper subset of <! node
596  switch (text[1]) {
597  case '-': // <!-
598  if (text[2] == '-') {
599  // '<!--' - xml comment
600  text += 3; // skip '!--'
601  parseComment(text);
602  return;
603  }
604  break;
605 
606  case '[': // <![
607  if (next<"CDATA[">(&text[2])) {
608  // '<![CDATA[' - cdata
609  text += 8; // skip '![CDATA['
610  parseCdata(text);
611  return;
612  }
613  break;
614 
615  case 'D': // <!D
616  if (next<"OCTYPE">(&text[2]) &&
617  WhitespacePred::test(text[8])) {
618  // '<!DOCTYPE ' - doctype
619  text += 9; // skip '!DOCTYPE '
620  parseDoctype(text);
621  return;
622  }
623  break;
624  }
625  // Attempt to skip other, unrecognized types starting with <!
626  ++text; // skip !
627  while (*text != '>') {
628  if (*text == 0) {
629  throw ParseError(
630  "unexpected end of data", text);
631  }
632  ++text;
633  }
634  ++text; // skip '>'
635  break;
636 
637  default: // <...
638  parseElement(text);
639  break;
640  }
641  }
642 
643  // Parse contents of the node - children, data etc.
644  void parseNodeContents(char*& text)
645  {
646  while (true) {
647  char* contentsStart = text; // start before ws is skipped
648  skip<WhitespacePred>(text); // Skip ws between > and contents
649 
650  switch (*text) {
651  case '<': // Node closing or child node
652 afterText: // After parseText() jump here instead of continuing
653  // the loop, because skipping whitespace is unnecessary.
654  if (text[1] == '/') {
655  // Node closing
656  text += 2; // skip '</'
657  skip<NodeNamePred>(text);
658  // TODO validate closing tag??
659  handler.stop();
660  // Skip remaining whitespace after node name
661  skip<WhitespacePred>(text);
662  if (*text != '>') {
663  throw ParseError("expected >", text);
664  }
665  ++text; // skip '>'
666  return;
667  } else {
668  // Child node
669  ++text; // skip '<'
670  parseNode(text);
671  }
672  break;
673 
674  case '\0':
675  throw ParseError("unexpected end of data", text);
676 
677  default:
678  parseText(text, contentsStart);
679  goto afterText;
680  }
681  }
682  }
683 
684  // Parse XML attributes of the node
685  void parseAttributes(char*& text, bool declaration)
686  {
687  // For all attributes
688  while (AttributeNamePred::test(*text)) {
689  // Extract attribute name
690  char* name = text;
691  ++text; // Skip first character of attribute name
692  skip<AttributeNamePred>(text);
693  char* nameEnd = text;
694  if (name == nameEnd) {
695  throw ParseError("expected attribute name", name);
696  }
697 
698  skip<WhitespacePred>(text); // skip ws after name
699  if (*text != '=') {
700  throw ParseError("expected =", text);
701  }
702  ++text; // skip =
703  skip<WhitespacePred>(text); // skip ws after =
704 
705  // Skip quote and remember if it was ' or "
706  char quote = *text;
707  if (quote != one_of('\'', '"')) {
708  throw ParseError("expected ' or \"", text);
709  }
710  ++text;
711 
712  // Extract attribute value and expand char refs in it
713  // No whitespace normalization in attributes
714  constexpr int FLAGS2 = FLAGS & ~normalizeWhitespace;
715  char* value = text;
716  char* valueEnd = (quote == '\'')
717  ? skipAndExpand<AttPred1, AttPurePred1, FLAGS2>(text)
718  : skipAndExpand<AttPred2, AttPurePred2, FLAGS2>(text);
719  // Make sure that end quote is present
720  // check before calling handler.xxx()
721  if (*text != quote) {
722  throw ParseError("expected ' or \"", text);
723  }
724  ++text; // skip quote
725 
726  if (FLAGS & zeroTerminateStrings) {
727  *nameEnd = '\0';
728  *valueEnd = '\0';
729  }
730  if (!declaration) {
731  handler.attribute(std::string_view(name, nameEnd - name),
732  std::string_view(value, valueEnd - value));
733  } else {
734  handler.declAttribute(std::string_view(name, nameEnd - name),
735  std::string_view(value, valueEnd - value));
736  }
737 
738  skip<WhitespacePred>(text); // skip ws after value
739  }
740  }
741 };
742 
743 } // namespace internal
744 
745 template<int FLAGS, typename HANDLER>
746 inline void parse(HANDLER& handler, char* xml)
747 {
748  internal::Parser<FLAGS, HANDLER> parser(handler, xml);
749 }
750 
751 } // namespace rapidsax
752 
753 #endif
Definition: one_of.hh:7
void declAttribute(std::string_view, std::string_view)
Definition: rapidsax.hh:93
void text(std::string_view)
Definition: rapidsax.hh:78
void comment(std::string_view)
Definition: rapidsax.hh:88
void procInstr(std::string_view, std::string_view)
Definition: rapidsax.hh:100
void attribute(std::string_view, std::string_view)
Definition: rapidsax.hh:82
void doctype(std::string_view)
Definition: rapidsax.hh:97
void cdata(std::string_view)
Definition: rapidsax.hh:85
void start(std::string_view)
Definition: rapidsax.hh:64
char * where() const
Definition: rapidsax.hh:114
const char * what() const
Definition: rapidsax.hh:113
ParseError(const char *what_, char *where_)
Definition: rapidsax.hh:107
Parser(HANDLER &handler_, char *text)
Definition: rapidsax.hh:354
void insertUTF8char(char *&text, uint32_t code)
Definition: rapidsax.hh:180
const uint8_t lutDigits[256]
Definition: rapidsax.cc:36
void skipBOM(char *&text)
Definition: rapidsax.hh:341
const uint8_t lutChar[256]
Definition: rapidsax.cc:14
constexpr int noEntityTranslation
Definition: rapidsax.hh:47
constexpr int zeroTerminateStrings
Definition: rapidsax.hh:54
constexpr int trimWhitespace
Definition: rapidsax.hh:49
void parse(HANDLER &handler, char *xml)
Definition: rapidsax.hh:746
constexpr int normalizeWhitespace
Definition: rapidsax.hh:52
constexpr size_t EXTRA_BUFFER_SPACE
Definition: rapidsax.hh:41
static bool test(char ch)
Definition: rapidsax.hh:161
static bool test(char ch)
Definition: rapidsax.hh:165
static bool test(char ch)
Definition: rapidsax.hh:171
static bool test(char ch)
Definition: rapidsax.hh:176
static bool test(char ch)
Definition: rapidsax.hh:134
static bool test(char ch)
Definition: rapidsax.hh:144
static bool test(char ch)
Definition: rapidsax.hh:129
constexpr auto end(const zstring_view &x)