fdd7a24129d92588f9bd15231c01df942e3d77ef
[tpot/pegasus.git.bak/pegasus.git] / src / Pegasus / Common / XmlParser.cpp
1 //%LICENSE////////////////////////////////////////////////////////////////
2 //
3 // Licensed to The Open Group (TOG) under one or more contributor license
4 // agreements.  Refer to the OpenPegasusNOTICE.txt file distributed with
5 // this work for additional information regarding copyright ownership.
6 // Each contributor licenses this file to you under the OpenPegasus Open
7 // Source License; you may not use this file except in compliance with the
8 // License.
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a
11 // copy of this software and associated documentation files (the "Software"),
12 // to deal in the Software without restriction, including without limitation
13 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 // and/or sell copies of the Software, and to permit persons to whom the
15 // Software is furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included
18 // in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 //////////////////////////////////////////////////////////////////////////
29 //
30 //%/////////////////////////////////////////////////////////////////////////////
31
32 ////////////////////////////////////////////////////////////////////////////////
33 //
34 // XmlParser
35 //
36 //      This file contains a simple non-validating XML parser. Here are
37 //      serveral rules for well-formed XML:
38 //
39 //          1.  Documents must begin with an XML declaration:
40 //
41 //              <?xml version="1.0" standalone="yes"?>
42 //
43 //          2.  Comments have the form:
44 //
45 //              <!-- blah blah blah -->
46 //
47 //          3. The following entity references are supported:
48 //
49 //              &amp - ampersand
50 //              &lt - less-than
51 //              &gt - greater-than
52 //              &quot - full quote
53 //              &apos - apostrophe
54 //
55 //             as well as character (numeric) references:
56 //
57 //              &#49; - decimal reference for character '1'
58 //              &#x31; - hexadecimal reference for character '1'
59 //
60 //          4. Element names and attribute names take the following form:
61 //
62 //              [A-Za-z_][A-Za-z_0-9-.:]
63 //
64 //          5.  Arbitrary data (CDATA) can be enclosed like this:
65 //
66 //                  <![CDATA[
67 //                  ...
68 //                  ]]>
69 //
70 //          6.  Element names and attributes names are case-sensitive.
71 //
72 //          7.  XmlAttribute values must be delimited by full or half quotes.
73 //              XmlAttribute values must be delimited.
74 //
75 //          8.  <!DOCTYPE...>
76 //
77 // TODO:
78 //
79 //      ATTN: KS P1 4 Mar 2002. Review the following TODOs to see if there is
80 //      work. Handle <!DOCTYPE...> sections which are complicated (containing
81 //        rules rather than references to files).
82 //
83 //      Remove newlines from string literals:
84 //
85 //          Example: <xyz x="hello
86 //              world">
87 //
88 ////////////////////////////////////////////////////////////////////////////////
89
90 #include <Pegasus/Common/Config.h>
91 #include <cctype>
92 #include <cstdio>
93 #include <cstdlib>
94 #include <cstring>
95 #include "XmlParser.h"
96 #include "Logger.h"
97 #include "ExceptionRep.h"
98 #include "CharSet.h"
99
100 PEGASUS_NAMESPACE_BEGIN
101
102 ////////////////////////////////////////////////////////////////////////////////
103 //
104 // Static helper functions
105 //
106 ////////////////////////////////////////////////////////////////////////////////
107
108 static void _printValue(const char* p)
109 {
110     for (; *p; p++)
111     {
112         if (*p == '\n')
113             PEGASUS_STD(cout) << "\\n";
114         else if (*p == '\r')
115             PEGASUS_STD(cout) << "\\r";
116         else if (*p == '\t')
117             PEGASUS_STD(cout) << "\\t";
118         else
119             PEGASUS_STD(cout) << *p;
120     }
121 }
122
123 struct EntityReference
124 {
125     const char* match;
126     Uint32 length;
127     char replacement;
128 };
129
130 // ATTN: Add support for more entity references
131 static EntityReference _references[] =
132 {
133     { "&amp;", 5, '&' },
134     { "&lt;", 4, '<' },
135     { "&gt;", 4, '>' },
136     { "&quot;", 6, '"' },
137     { "&apos;", 6, '\'' }
138 };
139
140
141 // Implements a check for a whitespace character, without calling
142 // isspace( ).  The isspace( ) function is locale-sensitive,
143 // and incorrectly flags some chars above 0x7f as whitespace.  This
144 // causes the XmlParser to incorrectly parse UTF-8 data.
145 //
146 // Section 2.3 of XML 1.0 Standard (http://www.w3.org/TR/REC-xml)
147 // defines white space as:
148 // S    ::=    (#x20 | #x9 | #xD | #xA)+
149 static inline int _isspace(char c)
150 {
151     return CharSet::isXmlWhiteSpace((Uint8)c);
152 }
153
154 static Uint32 _REFERENCES_SIZE = (sizeof(_references) / sizeof(_references[0]));
155
156 ////////////////////////////////////////////////////////////////////////////////
157 //
158 // XmlException
159 //
160 ////////////////////////////////////////////////////////////////////////////////
161
162 static const char* _xmlMessages[] =
163 {
164     "Bad opening element",
165     "Bad closing element",
166     "Bad attribute name",
167     "Exepected equal sign",
168     "Bad attribute value",
169     "A \"--\" sequence found within comment",
170     "Unterminated comment",
171     "Unterminated CDATA block",
172     "Unterminated DOCTYPE",
173     "Malformed reference",
174     "Expected a comment or CDATA following \"<!\" sequence",
175     "Closing element does not match opening element",
176     "One or more tags are still open",
177     "More than one root element was encountered",
178     "Validation error",
179     "Semantic error",
180     "Namespace not declared"
181 };
182
183 static const char* _xmlKeys[] =
184 {
185     "Common.XmlParser.BAD_START_TAG",
186     "Common.XmlParser.BAD_END_TAG",
187     "Common.XmlParser.BAD_ATTRIBUTE_NAME",
188     "Common.XmlParser.EXPECTED_EQUAL_SIGN",
189     "Common.XmlParser.BAD_ATTRIBUTE_VALUE",
190     "Common.XmlParser.MINUS_MINUS_IN_COMMENT",
191     "Common.XmlParser.UNTERMINATED_COMMENT",
192     "Common.XmlParser.UNTERMINATED_CDATA",
193     "Common.XmlParser.UNTERMINATED_DOCTYPE",
194     "Common.XmlParser.MALFORMED_REFERENCE",
195     "Common.XmlParser.EXPECTED_COMMENT_OR_CDATA",
196     "Common.XmlParser.START_END_MISMATCH",
197     "Common.XmlParser.UNCLOSED_TAGS",
198     "Common.XmlParser.MULTIPLE_ROOTS",
199     "Common.XmlParser.VALIDATION_ERROR",
200     "Common.XmlParser.SEMANTIC_ERROR",
201     "Common.XmlParser.UNDECLARED_NAMESPACE"
202 };
203
204
205 static MessageLoaderParms _formMessage(
206     Uint32 code,
207     Uint32 line,
208     const String& message)
209 {
210     String dftMsg = _xmlMessages[Uint32(code) - 1];
211     const char* key = _xmlKeys[Uint32(code) - 1];
212     String msg = message;
213
214     dftMsg.append(": on line $0");
215     if (message.size())
216     {
217         msg = ": " + msg;
218         dftMsg.append("$1");
219     }
220
221     return MessageLoaderParms(key, dftMsg.getCString(), line ,msg);
222 }
223
224 static MessageLoaderParms _formPartialMessage(Uint32 code, Uint32 line)
225 {
226     String dftMsg = _xmlMessages[Uint32(code) - 1];
227     const char* key = _xmlKeys[Uint32(code) - 1];
228
229     dftMsg.append(": on line $0");
230
231     return MessageLoaderParms(key, dftMsg.getCString(), line);
232 }
233
234
235 XmlException::XmlException(
236     XmlException::Code code,
237     Uint32 lineNumber,
238     const String& message)
239     : Exception(_formMessage(code, lineNumber, message))
240 {
241
242 }
243
244
245 XmlException::XmlException(
246     XmlException::Code code,
247     Uint32 lineNumber,
248     MessageLoaderParms& msgParms)
249     : Exception(_formPartialMessage(code, lineNumber))
250 {
251         if (msgParms.default_msg.size())
252     {
253         msgParms.default_msg = ": " + msgParms.default_msg;
254     }
255         _rep->message.append(MessageLoader::getMessage(msgParms));
256 }
257
258
259 ////////////////////////////////////////////////////////////////////////////////
260 //
261 // XmlValidationError
262 //
263 ////////////////////////////////////////////////////////////////////////////////
264
265 XmlValidationError::XmlValidationError(
266     Uint32 lineNumber,
267     const String& message)
268     : XmlException(XmlException::VALIDATION_ERROR, lineNumber, message)
269 {
270 }
271
272
273 XmlValidationError::XmlValidationError(
274     Uint32 lineNumber,
275     MessageLoaderParms& msgParms)
276     : XmlException(XmlException::VALIDATION_ERROR, lineNumber, msgParms)
277 {
278 }
279
280
281 ////////////////////////////////////////////////////////////////////////////////
282 //
283 // XmlSemanticError
284 //
285 ////////////////////////////////////////////////////////////////////////////////
286
287 XmlSemanticError::XmlSemanticError(
288     Uint32 lineNumber,
289     const String& message)
290     : XmlException(XmlException::SEMANTIC_ERROR, lineNumber, message)
291 {
292 }
293
294
295 XmlSemanticError::XmlSemanticError(
296     Uint32 lineNumber,
297     MessageLoaderParms& msgParms)
298     : XmlException(XmlException::SEMANTIC_ERROR, lineNumber, msgParms)
299 {
300 }
301
302
303 ////////////////////////////////////////////////////////////////////////////////
304 //
305 // XmlParser
306 //
307 ////////////////////////////////////////////////////////////////////////////////
308
309 XmlParser::XmlParser(char* text, XmlNamespace* ns)
310     : _line(1),
311       _current(text),
312       _restoreChar('\0'),
313       _foundRoot(false),
314       _supportedNamespaces(ns),
315       // Start valid indexes with -2. -1 is reserved for not found.
316       _currentUnsupportedNSType(-2)
317 {
318 }
319
320 inline void _skipWhitespace(Uint32& line, char*& p)
321 {
322     while (*p && _isspace(*p))
323     {
324         if (*p == '\n')
325             line++;
326
327         p++;
328     }
329 }
330
331 #if defined(PEGASUS_PLATFORM_WIN64_IA64_MSVC) || \
332     defined(PEGASUS_PLATFORM_WIN64_X86_64_MSVC)
333 #pragma optimize( "", off )
334 #endif
335 static int _getEntityRef(char*& p)
336 {
337     if ((p[0] == 'g') && (p[1] == 't') && (p[2] == ';'))
338     {
339         p += 3;
340         return '>';
341     }
342
343     if ((p[0] == 'l') && (p[1] == 't') && (p[2] == ';'))
344     {
345         p += 3;
346         return '<';
347     }
348
349     if ((p[0] == 'a') && (p[1] == 'p') && (p[2] == 'o') && (p[3] == 's') &&
350         (p[4] == ';'))
351     {
352         p += 5;
353         return '\'';
354     }
355
356     if ((p[0] == 'q') && (p[1] == 'u') && (p[2] == 'o') && (p[3] == 't') &&
357         (p[4] == ';'))
358     {
359         p += 5;
360         return '"';
361     }
362
363     if ((p[0] == 'a') && (p[1] == 'm') && (p[2] == 'p') && (p[3] == ';'))
364     {
365         p += 4;
366         return '&';
367     }
368
369     return -1;
370 }
371 #if defined(PEGASUS_PLATFORM_WIN64_IA64_MSVC) || \
372     defined(PEGASUS_PLATFORM_WIN64_X86_64_MSVC)
373 #pragma optimize( "", on )
374 #endif
375
376 static inline int _getCharRef(char*& p)
377 {
378     char* end;
379     unsigned long ch;
380     Boolean hex = false;
381
382     if (*p == 'x')
383     {
384         hex = true;
385         ch = strtoul(++p, &end, 16);
386     }
387     else
388     {
389         ch = strtoul(p, &end, 10);
390     }
391
392     if ((end == p) || (*end != ';') || (ch > 255))
393     {
394         return -1;
395     }
396
397     if ((hex && (end - p > 4)) || (!hex && (end - p > 5)))
398     {
399         return -1;
400     }
401
402     p = end + 1;
403
404     return ch;
405 }
406
407 // Parse an entity reference or a character reference
408 static inline int _getRef(Uint32 line, char*& p)
409 {
410     int ch;
411
412     if (*p == '#')
413     {
414         ch = _getCharRef(++p);
415     }
416     else
417     {
418         ch = _getEntityRef(p);
419     }
420
421     if (ch == -1)
422     {
423         throw XmlException(XmlException::MALFORMED_REFERENCE, line);
424     }
425
426     return ch;
427 }
428
429 static inline void _normalizeElementValue(
430     Uint32& line,
431     char*& p,
432     Uint32 &textLen)
433 {
434     // Process one character at a time:
435
436     char* q = p;
437     char *start = p;
438
439     while (*p && (*p != '<'))
440     {
441         if (_isspace(*p))
442         {
443             // Trim whitespace from the end of the value, but do not compress
444             // whitespace within the value.
445
446             const char* start = p;
447
448             if (*p++ == '\n')
449             {
450                 line++;
451             }
452
453             _skipWhitespace(line, p);
454
455             if (*p && (*p != '<'))
456             {
457                 // Transfer internal whitespace to q without compressing it.
458                 const char* i = start;
459                 while (i < p)
460                 {
461                     *q++ = *i++;
462                 }
463             }
464             else
465             {
466                 // Do not transfer trailing whitespace to q.
467                 break;
468             }
469         }
470         else if (*p == '&')
471         {
472             // Process an entity reference or a character reference.
473
474             *q++ = _getRef(line, ++p);
475         }
476         else
477         {
478             *q++ = *p++;
479         }
480     }
481
482     // If q got behind p, it is safe and necessary to null-terminate q
483
484     if (q != p)
485     {
486         *q = '\0';
487     }
488     textLen = (Uint32)(q - start);
489 }
490
491 static inline void _normalizeAttributeValue(
492     Uint32& line,
493     char*& p,
494     char end_char,
495     char*& start)
496 {
497     // Skip over leading whitespace:
498
499     _skipWhitespace(line, p);
500     start = p;
501
502     // Process one character at a time:
503
504     char* q = p;
505
506     while (*p && (*p != end_char))
507     {
508         if (_isspace(*p))
509         {
510             // Compress sequences of whitespace characters to a single space
511             // character. Update line number when newlines encountered.
512
513             if (*p++ == '\n')
514             {
515                 line++;
516             }
517
518             *q++ = ' ';
519
520             _skipWhitespace(line, p);
521         }
522         else if (*p == '&')
523         {
524             // Process an entity reference or a character reference.
525
526             *q++ = _getRef(line, ++p);
527         }
528         else
529         {
530             *q++ = *p++;
531         }
532     }
533
534     // Remove single trailing whitespace (consecutive whitespaces already
535     // compressed above).  Since p >= q, we can tell if we need to strip a
536     // trailing space from q by looking at the end of p.  We must not look at
537     // the last character of p, though, if p is an empty string.
538     Boolean adjust_q = (p != start) && _isspace(p[-1]);
539
540     // We encountered a the end_char or a zero-terminator.
541
542     *q = *p;
543
544     if (adjust_q)
545     {
546         q--;
547     }
548
549     // If q got behind p, it is safe and necessary to null-terminate q
550
551     if (q != p)
552     {
553         *q = '\0';
554     }
555 }
556
557 Boolean XmlParser::next(
558     XmlEntry& entry,
559     Boolean includeComment)
560 {
561     if (!_putBackStack.isEmpty())
562     {
563         entry = _putBackStack.top();
564         _putBackStack.pop();
565         return true;
566     }
567
568     // If a character was overwritten with a null-terminator the last
569     // time this routine was called, then put back that character. Before
570     // exiting of course, restore the null-terminator.
571
572     char* nullTerminator = 0;
573
574     if (_restoreChar && !*_current)
575     {
576         nullTerminator = _current;
577         *_current = _restoreChar;
578         _restoreChar = '\0';
579     }
580
581     entry.attributes.clear();
582
583     if (_supportedNamespaces)
584     {
585         // Remove namespaces of a deeper scope level from the stack.
586         while (!_nameSpaces.isEmpty() &&
587                _nameSpaces.top().scopeLevel > _stack.size())
588         {
589             _nameSpaces.pop();
590         }
591     }
592
593     // Loop until we are done with comments if includeComment is false.
594     do
595     {
596         // Skip over any whitespace:
597         _skipWhitespace(_line, _current);
598
599         if (!*_current)
600         {
601             if (nullTerminator)
602                 *nullTerminator = '\0';
603
604             if (!_stack.isEmpty())
605                 throw XmlException(XmlException::UNCLOSED_TAGS, _line);
606
607             return false;
608         }
609
610         // Either a "<...>" or content begins next:
611
612         if (*_current == '<')
613         {
614             _current++;
615             _getElement(_current, entry);
616
617             if (nullTerminator)
618                 *nullTerminator = '\0';
619
620             if (entry.type == XmlEntry::START_TAG)
621             {
622                 if (_stack.isEmpty() && _foundRoot)
623                     throw XmlException(XmlException::MULTIPLE_ROOTS, _line);
624
625                 _foundRoot = true;
626                 _stack.push((char*)entry.text);
627             }
628             else if (entry.type == XmlEntry::END_TAG)
629             {
630                 if (_stack.isEmpty())
631                     throw XmlException(XmlException::START_END_MISMATCH, _line);
632
633                 if (strcmp(_stack.top(), entry.text) != 0)
634                     throw XmlException(XmlException::START_END_MISMATCH, _line);
635
636                 _stack.pop();
637             }
638         }
639         else
640         {
641             // Normalize the content:
642
643             char* start = _current;
644             Uint32 textLen;
645             _normalizeElementValue(_line, _current, textLen);
646
647             // Get the content:
648
649             entry.type = XmlEntry::CONTENT;
650             entry.text = start;
651             entry.textLen = textLen;
652
653             // Overwrite '<' with a null character (temporarily).
654
655             _restoreChar = *_current;
656             *_current = '\0';
657
658             if (nullTerminator)
659                 *nullTerminator = '\0';
660         }
661     } while (!includeComment && entry.type == XmlEntry::COMMENT);
662
663     if (_supportedNamespaces &&
664         (entry.type == XmlEntry::START_TAG ||
665          entry.type == XmlEntry::EMPTY_TAG ||
666          entry.type == XmlEntry::END_TAG))
667     {
668         // Determine the namespace type for this entry
669
670         if (entry.type == XmlEntry::START_TAG ||
671             entry.type == XmlEntry::EMPTY_TAG)
672         {
673             // Process namespace declarations and determine the namespace type
674             // for the attributes.
675
676             Uint32 scopeLevel = _stack.size();
677             if (entry.type == XmlEntry::EMPTY_TAG)
678             {
679                 // Empty tags are deeper scope, but not pushed onto the stack
680                 scopeLevel++;
681             }
682
683             for (Uint32 i = 0, n = entry.attributes.size(); i < n; i++)
684             {
685                 XmlAttribute& attr = entry.attributes[i];
686                 if ((strncmp(attr.name, "xmlns:", 6) == 0) ||
687                     (strcmp(attr.name, "xmlns") == 0))
688                 {
689                     // Process a namespace declaration
690                     XmlNamespace ns;
691                     if (attr.name[5] == ':')
692                     {
693                         ns.localName = attr.localName;
694                     }
695                     else
696                     {
697                         // Default name space has no local name
698                         ns.localName = 0;
699                     }
700                     ns.extendedName = attr.value;
701                     ns.scopeLevel = scopeLevel;
702                     ns.type = _getSupportedNamespaceType(ns.extendedName);
703
704                     // If the namespace is not supported, assign it a unique
705                     // negative identifier.
706                     if (ns.type == -1)
707                     {
708                         ns.type = _currentUnsupportedNSType--;
709                     }
710
711                     _nameSpaces.push(ns);
712                 }
713                 else
714                 {
715                     // Get the namespace type for this attribute.
716                     attr.nsType = _getNamespaceType(attr.name);
717                 }
718             }
719         }
720
721         entry.nsType = _getNamespaceType(entry.text);
722     }
723     else
724     {
725         entry.nsType = -1;
726     }
727
728     return true;
729 }
730
731 // Get the namespace type of the given tag
732 int XmlParser::_getNamespaceType(const char* tag)
733 {
734     const char* pos = strchr(tag, ':');
735
736     // If ':' is not found, the tag is not namespace qualified and we
737     // need to look for the default name space.
738
739     // Search the namespace stack from the top
740     for (Sint32 i = _nameSpaces.size() - 1; i >=0; i--)
741     {
742         // If ':' is found, look for the name space with the matching
743         // local name...
744         if ((pos && _nameSpaces[i].localName &&
745              !strncmp(_nameSpaces[i].localName, tag, pos - tag)) ||
746             // ... otherwise look for the default name space. It's the
747             // one with localName set to NULL
748             (!pos && !_nameSpaces[i].localName))
749         {
750             return _nameSpaces[i].type;
751         }
752     }
753
754     // If the tag is namespace qualified, but the name space has not been
755     // declared, it's malformed XML and we must throw an exception.
756     // Note:  The "xml" namespace is specifically defined by the W3C as a
757     // reserved prefix ("http://www.w3.org/XML/1998/namespace").
758     if (pos && (strncmp(tag, "xml:", 4) != 0))
759     {
760         throw XmlException(XmlException::UNDECLARED_NAMESPACE, _line);
761     }
762
763     // Otherwise it's OK not to have a name space.
764     return -1;
765 }
766
767 // Given the extended namespace name, find it in the table of supported
768 // namespaces and return its type.
769 int XmlParser::_getSupportedNamespaceType(const char* extendedName)
770 {
771     for (Sint32 i = 0;
772          _supportedNamespaces[i].localName != 0;
773          i++)
774     {
775         PEGASUS_ASSERT(_supportedNamespaces[i].type == i);
776         if (!strcmp(_supportedNamespaces[i].extendedName, extendedName))
777         {
778             return _supportedNamespaces[i].type;
779         }
780     }
781     return -1;
782 }
783
784 XmlNamespace* XmlParser::getNamespace(int nsType)
785 {
786     for (Sint32 i = _nameSpaces.size() - 1; i >=0; i--)
787     {
788         if (_nameSpaces[i].type == nsType)
789         {
790             return &_nameSpaces[i];
791         }
792     }
793     return 0;
794 }
795
796 void XmlParser::putBack(XmlEntry& entry)
797 {
798     _putBackStack.push(entry);
799 }
800
801 XmlParser::~XmlParser()
802 {
803     // Nothing to do!
804 }
805
806 // A-Za-z0-9_-.  (Note that ':' is not included and must be checked separately)
807 static unsigned char _isInnerElementChar[] =
808 {
809     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
810     0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,
811     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
812     1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
813     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
814     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
815     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
816 };
817
818 inline Boolean _getQName(char*& p, const char*& localName)
819 {
820     localName = p;
821
822     if (!CharSet::isAlNumUnder(Uint8(*p)))
823         return false;
824
825     p++;
826
827     // No explicit test for NULL termination is needed.
828     // On position 0 of the array false is returned.
829     while (_isInnerElementChar[Uint8(*p)])
830         p++;
831
832     // We've validated the prefix, now validate the local name
833     if (*p == ':')
834     {
835         localName = ++p;
836
837         if (!CharSet::isAlNumUnder(Uint8(*p)))
838             return false;
839
840         p++;
841         // No explicit test for NULL termination is needed.
842         // On position 0 of the array false is returned.
843         while (_isInnerElementChar[Uint8(*p)])
844             p++;
845     }
846
847     return true;
848 }
849
850 Boolean XmlParser::_getElementName(char*& p, const char*& localName)
851 {
852     if (!_getQName(p, localName))
853         throw XmlException(XmlException::BAD_START_TAG, _line);
854
855     // The next character must be a space:
856
857     if (_isspace(*p))
858     {
859         *p++ = '\0';
860         _skipWhitespace(_line, p);
861     }
862
863     if (*p == '>')
864     {
865         *p++ = '\0';
866         return true;
867     }
868
869     return false;
870 }
871
872 Boolean XmlParser::_getOpenElementName(
873     char*& p,
874     const char*& localName,
875     Boolean& openCloseElement)
876 {
877     openCloseElement = false;
878
879     if (!_getQName(p, localName))
880         throw XmlException(XmlException::BAD_START_TAG, _line);
881
882     // The next character must be a space:
883
884     if (_isspace(*p))
885     {
886         *p++ = '\0';
887         _skipWhitespace(_line, p);
888     }
889
890     if (*p == '>')
891     {
892         *p++ = '\0';
893         return true;
894     }
895
896     if (p[0] == '/' && p[1] == '>')
897     {
898         openCloseElement = true;
899         *p = '\0';
900         p += 2;
901         return true;
902     }
903
904     return false;
905 }
906
907 void XmlParser::_getAttributeNameAndEqual(char*& p, const char*& localName)
908 {
909     if (!_getQName(p, localName))
910         throw XmlException(XmlException::BAD_ATTRIBUTE_NAME, _line);
911
912     char* term = p;
913
914     _skipWhitespace(_line, p);
915
916     if (*p != '=')
917         throw XmlException(XmlException::BAD_ATTRIBUTE_NAME, _line);
918
919     p++;
920
921     _skipWhitespace(_line, p);
922
923     *term = '\0';
924 }
925
926 void XmlParser::_getComment(char*& p)
927 {
928     // Now p points to first non-whitespace character beyond "<--" sequence:
929
930     for (; *p; p++)
931     {
932         if (p[0] == '-' && p[1] == '-')
933         {
934             if (p[2] != '>')
935             {
936                 throw XmlException(
937                     XmlException::MINUS_MINUS_IN_COMMENT, _line);
938             }
939
940             // Find end of comment (excluding whitespace):
941
942             *p = '\0';
943             p += 3;
944             return;
945         }
946     }
947
948     // If it got this far, then the comment is unterminated:
949
950     throw XmlException(XmlException::UNTERMINATED_COMMENT, _line);
951 }
952
953 void XmlParser::_getCData(char*& p)
954 {
955     // At this point p points one past "<![CDATA[" sequence:
956
957     for (; *p; p++)
958     {
959         if (p[0] == ']' && p[1] == ']' && p[2] == '>')
960         {
961             *p = '\0';
962             p += 3;
963             return;
964         }
965         else if (*p == '\n')
966             _line++;
967     }
968
969     // If it got this far, then the comment is unterminated:
970
971     throw XmlException(XmlException::UNTERMINATED_CDATA, _line);
972 }
973
974 void XmlParser::_getDocType(char*& p)
975 {
976     // Just ignore the DOCTYPE command for now:
977
978     for (; *p && *p != '>'; p++)
979     {
980         if (*p == '\n')
981             _line++;
982     }
983
984     if (*p != '>')
985         throw XmlException(XmlException::UNTERMINATED_DOCTYPE, _line);
986
987     p++;
988 }
989
990 void XmlParser::_getElement(char*& p, XmlEntry& entry)
991 {
992     //--------------------------------------------------------------------------
993     // Get the element name (expect one of these: '?', '!', [A-Za-z_])
994     //--------------------------------------------------------------------------
995
996     if (*p == '?')
997     {
998         entry.type = XmlEntry::XML_DECLARATION;
999         entry.text = ++p;
1000
1001         if (_getElementName(p, entry.localName))
1002             return;
1003     }
1004     else if (*p == '!')
1005     {
1006         p++;
1007
1008         // Expect a comment or CDATA:
1009
1010         if (p[0] == '-' && p[1] == '-')
1011         {
1012             p += 2;
1013             entry.type = XmlEntry::COMMENT;
1014             entry.text = p;
1015             _getComment(p);
1016             return;
1017         }
1018         else if (memcmp(p, "[CDATA[", 7) == 0)
1019         {
1020             p += 7;
1021             entry.type = XmlEntry::CDATA;
1022             entry.text = p;
1023             _getCData(p);
1024             entry.textLen = strlen(entry.text);
1025             return;
1026         }
1027         else if (memcmp(p, "DOCTYPE", 7) == 0)
1028         {
1029             entry.type = XmlEntry::DOCTYPE;
1030             entry.text = "";
1031             _getDocType(p);
1032             return;
1033         }
1034         throw(XmlException(XmlException::EXPECTED_COMMENT_OR_CDATA, _line));
1035     }
1036     else if (*p == '/')
1037     {
1038         entry.type = XmlEntry::END_TAG;
1039         entry.text = ++p;
1040
1041         if (!_getElementName(p, entry.localName))
1042             throw(XmlException(XmlException::BAD_END_TAG, _line));
1043
1044         return;
1045     }
1046     else if (CharSet::isAlphaUnder(Uint8(*p)))
1047     {
1048         entry.type = XmlEntry::START_TAG;
1049         entry.text = p;
1050
1051         Boolean openCloseElement = false;
1052
1053         if (_getOpenElementName(p, entry.localName, openCloseElement))
1054         {
1055             if (openCloseElement)
1056                 entry.type = XmlEntry::EMPTY_TAG;
1057             return;
1058         }
1059     }
1060     else
1061         throw XmlException(XmlException::BAD_START_TAG, _line);
1062
1063     //--------------------------------------------------------------------------
1064     // Grab all the attributes:
1065     //--------------------------------------------------------------------------
1066
1067     for (;;)
1068     {
1069         if (entry.type == XmlEntry::XML_DECLARATION)
1070         {
1071             if (p[0] == '?' && p[1] == '>')
1072             {
1073                 p += 2;
1074                 return;
1075             }
1076         }
1077         else if (entry.type == XmlEntry::START_TAG && p[0] == '/' && p[1] =='>')
1078         {
1079             entry.type = XmlEntry::EMPTY_TAG;
1080             p += 2;
1081             return;
1082         }
1083         else if (*p == '>')
1084         {
1085             p++;
1086             return;
1087         }
1088
1089         XmlAttribute attr;
1090         attr.nsType = -1;
1091         attr.name = p;
1092         _getAttributeNameAndEqual(p, attr.localName);
1093
1094         // Get the attribute value (e.g., "some value")
1095         {
1096             if ((*p != '"') && (*p != '\''))
1097             {
1098                 throw XmlException(XmlException::BAD_ATTRIBUTE_VALUE, _line);
1099             }
1100
1101             char quote = *p++;
1102
1103             char* start;
1104             _normalizeAttributeValue(_line, p, quote, start);
1105             attr.value = start;
1106
1107             if (*p != quote)
1108             {
1109                 throw XmlException(XmlException::BAD_ATTRIBUTE_VALUE, _line);
1110             }
1111
1112             // Overwrite the closing quote with a null-terminator:
1113
1114             *p++ = '\0';
1115         }
1116
1117         if (entry.type == XmlEntry::XML_DECLARATION)
1118         {
1119             // The next thing must a space or a "?>":
1120
1121             if (!(p[0] == '?' && p[1] == '>') && !_isspace(*p))
1122             {
1123                 throw XmlException(
1124                     XmlException::BAD_ATTRIBUTE_VALUE, _line);
1125             }
1126         }
1127         else if (!(*p == '>' || (p[0] == '/' && p[1] == '>') || _isspace(*p)))
1128         {
1129             // The next thing must be a space or a '>':
1130
1131             throw XmlException(XmlException::BAD_ATTRIBUTE_VALUE, _line);
1132         }
1133
1134         _skipWhitespace(_line, p);
1135
1136         entry.attributes.append(attr);
1137     }
1138 }
1139
1140 static const char* _typeStrings[] =
1141 {
1142     "XML_DECLARATION",
1143     "START_TAG",
1144     "EMPTY_TAG",
1145     "END_TAG",
1146     "COMMENT",
1147     "CDATA",
1148     "DOCTYPE",
1149     "CONTENT"
1150 };
1151
1152 void XmlEntry::print() const
1153 {
1154     PEGASUS_STD(cout) << "=== " << _typeStrings[type] << " ";
1155
1156     Boolean needQuotes = type == XmlEntry::CDATA || type == XmlEntry::CONTENT;
1157
1158     if (needQuotes)
1159         PEGASUS_STD(cout) << "\"";
1160
1161     _printValue(text);
1162
1163     if (needQuotes)
1164         PEGASUS_STD(cout) << "\"";
1165
1166     PEGASUS_STD(cout) << '\n';
1167
1168     for (Uint32 i = 0, n = attributes.size(); i < n; i++)
1169     {
1170         PEGASUS_STD(cout) << "    " << attributes[i].name << "=\"";
1171         _printValue(attributes[i].value);
1172         PEGASUS_STD(cout) << "\"" << PEGASUS_STD(endl);
1173     }
1174 }
1175
1176 const XmlAttribute* XmlEntry::findAttribute(
1177     const char* name) const
1178 {
1179     for (Uint32 i = 0, n = attributes.size(); i < n; i++)
1180     {
1181         if (strcmp(attributes[i].name, name) == 0)
1182             return &attributes[i];
1183     }
1184
1185     return 0;
1186 }
1187
1188 const XmlAttribute* XmlEntry::findAttribute(
1189     int attrNsType,
1190     const char* name) const
1191 {
1192     for (Uint32 i = 0, n = attributes.size(); i < n; i++)
1193     {
1194         if ((attributes[i].nsType == attrNsType) &&
1195             (strcmp(attributes[i].localName, name) == 0))
1196         {
1197             return &attributes[i];
1198         }
1199     }
1200
1201     return 0;
1202 }
1203
1204 // Find first non-whitespace character (set first) and last non-whitespace
1205 // character (set last one past this). For example, consider this string:
1206 //
1207 //      "   87     "
1208 //
1209 // The first pointer would point to '8' and the last pointer woudl point one
1210 // beyond '7'.
1211
1212 static void _findEnds(
1213     const char* str,
1214     const char*& first,
1215     const char*& last)
1216 {
1217     first = str;
1218
1219     while (_isspace(*first))
1220         first++;
1221
1222     if (!*first)
1223     {
1224         last = first;
1225         return;
1226     }
1227
1228     last = first + strlen(first);
1229
1230     while (last != first && _isspace(last[-1]))
1231         last--;
1232 }
1233
1234 Boolean XmlEntry::getAttributeValue(
1235     const char* name,
1236     Uint32& value) const
1237 {
1238     const XmlAttribute* attr = findAttribute(name);
1239
1240     if (!attr)
1241         return false;
1242
1243     const char* first;
1244     const char* last;
1245     _findEnds(attr->value, first, last);
1246
1247     char* end = 0;
1248     long tmp = strtol(first, &end, 10);
1249
1250     if (!end || end != last)
1251         return false;
1252
1253     value = Uint32(tmp);
1254     return true;
1255 }
1256
1257 Boolean XmlEntry::getAttributeValue(
1258     const char* name,
1259     Real32& value) const
1260 {
1261     const XmlAttribute* attr = findAttribute(name);
1262
1263     if (!attr)
1264         return false;
1265
1266     const char* first;
1267     const char* last;
1268     _findEnds(attr->value, first, last);
1269
1270     char* end = 0;
1271     double tmp = strtod(first, &end);
1272
1273     if (!end || end != last)
1274         return false;
1275
1276     value = static_cast<Real32>(tmp);
1277     return true;
1278 }
1279
1280 Boolean XmlEntry::getAttributeValue(
1281     const char* name,
1282     const char*& value) const
1283 {
1284     const XmlAttribute* attr = findAttribute(name);
1285
1286     if (!attr)
1287         return false;
1288
1289     value = attr->value;
1290     return true;
1291 }
1292
1293 Boolean XmlEntry::getAttributeValue(const char* name, String& value) const
1294 {
1295     const char* tmp;
1296
1297     if (!getAttributeValue(name, tmp))
1298         return false;
1299
1300     value = String(tmp);
1301     return true;
1302 }
1303
1304 void XmlAppendCString(Buffer& out, const char* str)
1305 {
1306     out.append(str, static_cast<Uint32>(strlen(str)));
1307 }
1308
1309 PEGASUS_NAMESPACE_END