1 mike 1.10 //%/////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2000, 2001 The Open group, BMC Software, Tivoli Systems, IBM
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to
7 // deal in the Software without restriction, including without limitation the
8 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 // sell copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
13 // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
14 // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15 // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
18 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 //
21 //==============================================================================
22 mike 1.10 //
23 // Author: Mike Brasher (mbrasher@bmc.com)
24 //
25 // Modified By:
26 //
27 //%/////////////////////////////////////////////////////////////////////////////
28
29 ////////////////////////////////////////////////////////////////////////////////
30 //
31 // XmlParser
32 //
33 // This file contains a simple non-validating XML parser. Here are
34 // serveral rules for well-formed XML:
35 //
36 // 1. Documents must begin with an XML declaration:
37 //
38 // <?xml version="1.0" standalone="yes"?>
39 //
40 // 2. Comments have the form:
41 //
42 // <!-- blah blah blah -->
43 mike 1.10 //
44 // 3. The following entity references are supported:
45 //
46 // & - ampersand
47 // < - less-than
48 // > - greater-than
49 // " - full quote
50 // &apos - apostrophe
51 //
52 // 4. Element names and attribute names take the following form:
53 //
54 // [A-Za-z_][A-Za-z_0-9-.:]
55 //
56 // 5. Arbitrary data (CDATA) can be enclosed like this:
57 //
58 // <![CDATA[
59 // ...
60 // ]]>
61 //
62 // 6. Element names and attributes names are case-sensitive.
63 //
64 mike 1.10 // 7. XmlAttribute values must be delimited by full or half quotes.
65 // XmlAttribute values must be delimited.
66 //
67 // 8. <!DOCTYPE...>
68 //
69 // TODO:
70 //
71 // Handle <!DOCTYPE...> sections which are complicated (containing
72 // rules rather than references to files).
73 //
74 // Handle reference of this form: "Α"
75 //
76 // Remove newlines from string literals:
77 //
78 // Example: <xyz x="hello
79 // world">
80 //
81 ////////////////////////////////////////////////////////////////////////////////
82
83 #include <cctype>
84 #include <cassert>
85 mike 1.10 #include <cstdio>
86 #include <cstdlib>
87 #include <cstring>
88 #include "XmlParser.h"
89 #include "Logger.h"
90
91 PEGASUS_NAMESPACE_BEGIN
92
93 #define PEGASUS_ARRAY_T XmlEntry
94 # include "ArrayImpl.h"
95 #undef PEGASUS_ARRAY_T
96
97
98 ////////////////////////////////////////////////////////////////////////////////
99 //
100 // Static helper functions
101 //
102 ////////////////////////////////////////////////////////////////////////////////
103
104 static void _printValue(const char* p)
105 {
106 mike 1.10 for (; *p; p++)
107 {
108 if (*p == '\n')
109 PEGASUS_STD(cout) << "\\n";
110 else if (*p == '\r')
111 PEGASUS_STD(cout) << "\\r";
112 else if (*p == '\t')
113 PEGASUS_STD(cout) << "\\t";
114 else
115 PEGASUS_STD(cout) << *p;
116 }
117 }
118
119 struct EntityReference
120 {
121 const char* match;
122 Uint32 length;
123 char replacement;
124 };
125
126 static EntityReference _references[] =
127 mike 1.10 {
128 { "&", 5, '&' },
129 { "<", 4, '<' },
130 { ">", 4, '>' },
131 { """, 6, '"' },
132 { "'", 6, '\'' }
133 };
134
135 static Uint32 _REFERENCES_SIZE = (sizeof(_references) / sizeof(_references[0]));
136
137 // Remove all redundant spaces from the given string:
138
139 static void _normalize(char* text)
140 {
141 Uint32 length = strlen(text);
142 char* p = text;
143 char* end = p + length;
144
145 // Remove leading spaces:
146
147 while (isspace(*p))
148 mike 1.10 p++;
149
150 if (p != text)
151 memmove(text, p, end - p + 1);
152
153 p = text;
154
155 // Look for sequences of more than one space and remove all but one.
156
157 for (;;)
158 {
159 // Advance to the next space:
160
161 while (*p && !isspace(*p))
162 p++;
163
164 if (!*p)
165 break;
166
167 // Advance to the next non-space:
168
169 mike 1.10 char* q = p++;
170
171 while (isspace(*p))
172 p++;
173
174 // Discard trailing spaces (if we are at the end):
175
176 if (!*p)
177 {
178 *q = '\0';
179 break;
180 }
181
182 // Remove the redundant spaces:
183
184 Uint32 n = p - q;
185
186 if (n > 1)
187 {
188 *q++ = ' ';
189 memmove(q, p, end - p + 1);
190 mike 1.10 p = q;
191 }
192 }
193 }
194
195 ////////////////////////////////////////////////////////////////////////////////
196 //
197 // XmlException
198 //
199 ////////////////////////////////////////////////////////////////////////////////
200
201 static const char* _xmlMessages[] =
202 {
203 "Bad opening element",
204 "Bad closing element",
205 "Bad attribute name",
206 "Exepected equal sign",
207 "Bad attribute value",
208 "A \"--\" sequence found within comment",
209 "Unterminated comment",
210 "Unterminated CDATA block",
211 mike 1.10 "Unterminated DOCTYPE",
212 "Too many attributes: parser only handles 10",
213 "Malformed reference",
214 "Expected a comment or CDATA following \"<!\" sequence",
215 "Closing element does not match opening element",
216 "One or more tags are still open",
217 "More than one root element was encountered",
218 "Validation error",
219 "Semantic error"
220 };
221
222 static String _formMessage(Uint32 code, Uint32 line, const String& message)
223 {
224 String result = _xmlMessages[Uint32(code) - 1];
225
226 char buffer[32];
227 sprintf(buffer, "%d", line);
228 result.append(": on line ");
229 result.append(buffer);
230
231 if (message.size())
232 mike 1.10 {
233 result.append(": ");
234 result.append(message);
235 }
236
237 return result;
238 }
239
240 XmlException::XmlException(
241 XmlException::Code code,
242 Uint32 lineNumber,
243 const String& message)
244 : Exception(_formMessage(code, lineNumber, message))
245 {
246
247 }
248
249 ////////////////////////////////////////////////////////////////////////////////
250 //
251 // XmlValidationError
252 //
253 mike 1.10 ////////////////////////////////////////////////////////////////////////////////
254
255 XmlValidationError::XmlValidationError(
256 Uint32 lineNumber,
257 const String& message)
258 : XmlException(XmlException::VALIDATION_ERROR, lineNumber, message)
259 {
260
261 }
262
263 ////////////////////////////////////////////////////////////////////////////////
264 //
265 // XmlSemanticError
266 //
267 ////////////////////////////////////////////////////////////////////////////////
268
269 XmlSemanticError::XmlSemanticError(
270 Uint32 lineNumber,
271 const String& message)
272 : XmlException(XmlException::SEMANTIC_ERROR, lineNumber, message)
273 {
274 mike 1.10
275 }
276
277 ////////////////////////////////////////////////////////////////////////////////
278 //
279 // XmlParser
280 //
281 ////////////////////////////////////////////////////////////////////////////////
282
283 XmlParser::XmlParser(char* text) : _line(1), _text(text), _current(text),
284 _restoreChar('\0'), _foundRoot(false)
285 {
286
287 }
288
289 Boolean XmlParser::next(XmlEntry& entry)
290 {
291 if (!_putBackStack.isEmpty())
292 {
293 entry = _putBackStack.top();
294 _putBackStack.pop();
295 mike 1.10 return true;
296 }
297
298 // If a character was overwritten with a null-terminator the last
299 // time this routine was called, then put back that character. Before
300 // exiting of course, restore the null-terminator.
301
302 char* nullTerminator = 0;
303
304 if (_restoreChar && !*_current)
305 {
306 nullTerminator = _current;
307 *_current = _restoreChar;
308 _restoreChar = '\0';
309 }
310
311 // Skip over any whitespace:
312
313 _skipWhitespace(_current);
314
315 if (!*_current)
316 mike 1.10 {
317 if (nullTerminator)
318 *nullTerminator = '\0';
319
320 if (!_stack.isEmpty())
321 throw XmlException(XmlException::UNCLOSED_TAGS, _line);
322
323 return false;
324 }
325
326 // Either a "<...>" or content begins next:
327
328 if (*_current == '<')
329 {
330 _current++;
331 _getElement(_current, entry);
332
333 if (nullTerminator)
334 *nullTerminator = '\0';
335
336 if (entry.type == XmlEntry::START_TAG)
337 mike 1.10 {
338 if (_stack.isEmpty() && _foundRoot)
339 throw XmlException(XmlException::MULTIPLE_ROOTS, _line);
340
341 _foundRoot = true;
342 _stack.push((char*)entry.text);
343 }
344 else if (entry.type == XmlEntry::END_TAG)
345 {
346 if (_stack.isEmpty())
347 throw XmlException(XmlException::START_END_MISMATCH, _line);
348
349 if (strcmp(_stack.top(), entry.text) != 0)
350 throw XmlException(XmlException::START_END_MISMATCH, _line);
351
352 _stack.pop();
353 }
354
355 return true;
356 }
357 else
358 mike 1.10 {
359 entry.type = XmlEntry::CONTENT;
360 entry.text = _current;
361 _getContent(_current);
362 _restoreChar = *_current;
363 *_current = '\0';
364
365 if (nullTerminator)
366 *nullTerminator = '\0';
367
368 _substituteReferences((char*)entry.text);
369 _normalize((char*)entry.text);
370
371 return true;
372 }
373 }
374
375 void XmlParser::putBack(XmlEntry& entry)
376 {
377 _putBackStack.push(entry);
378 }
379 mike 1.10
380 XmlParser::~XmlParser()
381 {
382 // Nothing to do!
383 }
384
385 void XmlParser::_skipWhitespace(char*& p)
386 {
387 while (*p && isspace(*p))
388 {
389 if (*p == '\n')
390 _line++;
391
392 p++;
393 }
394 }
395
396 Boolean XmlParser::_getElementName(char*& p)
397 {
398 if (!isalpha(*p) && *p != '_')
399 throw XmlException(XmlException::BAD_START_TAG, _line);
400 mike 1.10
401 while (*p &&
402 (isalnum(*p) || *p == '_' || *p == '-' || *p == ':' || *p == '.'))
403 p++;
404
405 // The next character must be a space:
406
407 if (isspace(*p))
408 {
409 *p++ = '\0';
410 _skipWhitespace(p);
411 }
412
413 if (*p == '>')
414 {
415 *p++ = '\0';
416 return true;
417 }
418
419 return false;
420 }
421 mike 1.10
422 Boolean XmlParser::_getOpenElementName(char*& p, Boolean& openCloseElement)
423 {
424 openCloseElement = false;
425
426 if (!isalpha(*p) && *p != '_')
427 throw XmlException(XmlException::BAD_START_TAG, _line);
428
429 while (*p &&
430 (isalnum(*p) || *p == '_' || *p == '-' || *p == ':' || *p == '.'))
431 p++;
432
433 // The next character must be a space:
434
435 if (isspace(*p))
436 {
437 *p++ = '\0';
438 _skipWhitespace(p);
439 }
440
441 if (*p == '>')
442 mike 1.10 {
443 *p++ = '\0';
444 return true;
445 }
446
447 if (p[0] == '/' && p[1] == '>')
448 {
449 openCloseElement = true;
450 *p = '\0';
451 p += 2;
452 return true;
453 }
454
455 return false;
456 }
457
458 void XmlParser::_getAttributeNameAndEqual(char*& p)
459 {
460 if (!isalpha(*p) && *p != '_')
461 throw XmlException(XmlException::BAD_ATTRIBUTE_NAME, _line);
462
463 mike 1.10 while (*p &&
464 (isalnum(*p) || *p == '_' || *p == '-' || *p == ':' || *p == '.'))
465 p++;
466
467 char* term = p;
468
469 _skipWhitespace(p);
470
471 if (*p != '=')
472 throw XmlException(XmlException::BAD_ATTRIBUTE_NAME, _line);
473
474 p++;
475
476 _skipWhitespace(p);
477
478 *term = '\0';
479 }
480
481 void XmlParser::_getAttributeValue(char*& p)
482 {
483 // ATTN-B: handle values contained in semiquotes:
484 mike 1.10
485 if (*p != '"' && *p != '\'')
486 throw XmlException(XmlException::BAD_ATTRIBUTE_VALUE, _line);
487
488 char startChar = *p++;
489
490 while (*p && *p != startChar)
491 p++;
492
493 if (*p != startChar)
494 throw XmlException(XmlException::BAD_ATTRIBUTE_VALUE, _line);
495
496 *p++ = '\0';
497 }
498
499 void XmlParser::_getComment(char*& p)
500 {
501 // Now p points to first non-whitespace character beyond "<--" sequence:
502
503 for (; *p; p++)
504 {
505 mike 1.10 if (p[0] == '-' && p[1] == '-')
506 {
507 if (p[2] != '>')
508 {
509 throw XmlException(
510 XmlException::MINUS_MINUS_IN_COMMENT, _line);
511 }
512
513 // Find end of comment (excluding whitespace):
514
515 *p = '\0';
516 p += 3;
517 return;
518 }
519 }
520
521 // If it got this far, then the comment is unterminated:
522
523 throw XmlException(XmlException::UNTERMINATED_COMMENT, _line);
524 }
525
526 mike 1.10 void XmlParser::_getCData(char*& p)
527 {
528 // At this point p points one past "<![CDATA[" sequence:
529
530 for (; *p; p++)
531 {
532 if (p[0] == ']' && p[1] == ']' && p[2] == '>')
533 {
534 *p = '\0';
535 p += 3;
536 return;
537 }
538 else if (*p == '\n')
539 _line++;
540 }
541
542 // If it got this far, then the comment is unterminated:
543
544 throw XmlException(XmlException::UNTERMINATED_CDATA, _line);
545 }
546
547 mike 1.10 void XmlParser::_getDocType(char*& p)
548 {
549 // Just ignore the DOCTYPE command for now:
550
551 for (; *p && *p != '>'; p++)
552 {
553 if (*p == '\n')
554 _line++;
555 }
556
557 if (*p != '>')
558 throw XmlException(XmlException::UNTERMINATED_DOCTYPE, _line);
559
560 p++;
561 }
562
563 void XmlParser::_getContent(char*& p)
564 {
565 while (*p && *p != '<')
566 {
567 if (*p == '\n')
568 mike 1.10 _line++;
569
570 p++;
571 }
572 }
573
574 void XmlParser::_substituteReferences(char* text)
575 {
576 Uint32 rem = strlen(text);
577
578 for (char* p = text; *p; p++, rem--)
579 {
580 if (*p == '&')
581 {
582 // Look for predefined entity reference:
583
584 Boolean found = false;
585
586 for (Uint32 i = 0; i < _REFERENCES_SIZE; i++)
587 {
588 Uint32 length = _references[i].length;
589 mike 1.10 const char* match = _references[i].match;
590
591 if (strncmp(p, _references[i].match, length) == 0)
592 {
593 found = true;
594 *p = _references[i].replacement;
595 char* q = p + length;
596 rem = rem - length + 1;
597 memmove(p + 1, q, rem);
598 }
599 }
600
601 // If not found, then at least make sure it is well formed:
602
603 if (!found)
604 {
605 char* start = p;
606 p++;
607
608 XmlException::Code code = XmlException::MALFORMED_REFERENCE;
609
610 mike 1.10 if (isalpha(*p) || *p == '_')
611 {
612 for (p++; *p && *p != ';'; p++)
613 {
614 if (!isalnum(*p) && *p != '_')
615 throw XmlException(code, _line);
616 }
617 }
618 else if (*p == '#')
619 {
620 for (p++ ; *p && *p != ';'; p++)
621 {
622 if (!isdigit(*p))
623 throw XmlException(code, _line);
624 }
625 }
626
627 if (*p != ';')
628 throw XmlException(code, _line);
629
630 rem -= p - start;
631 mike 1.10 }
632 }
633 }
634 }
635
636 static const char _EMPTY_STRING[] = "";
637
638 void XmlParser::_getElement(char*& p, XmlEntry& entry)
639 {
640 entry.attributeCount = 0;
641
642 //--------------------------------------------------------------------------
643 // Get the element name (expect one of these: '?', '!', [A-Za-z_])
644 //--------------------------------------------------------------------------
645
646 if (*p == '?')
647 {
648 entry.type = XmlEntry::XML_DECLARATION;
649 entry.text = ++p;
650
651 Boolean openCloseElement = false;
652 mike 1.10
653 if (_getElementName(p))
654 return;
655 }
656 else if (*p == '!')
657 {
658 p++;
659
660 // Expect a comment or CDATA:
661
662 if (p[0] == '-' && p[1] == '-')
663 {
664 p += 2;
665 entry.type = XmlEntry::COMMENT;
666 entry.text = p;
667 _getComment(p);
668 return;
669 }
670 else if (memcmp(p, "[CDATA[", 7) == 0)
671 {
672 p += 7;
673 mike 1.10 entry.type = XmlEntry::CDATA;
674 entry.text = p;
675 _getCData(p);
676 return;
677 }
678 else if (memcmp(p, "DOCTYPE", 7) == 0)
679 {
680 entry.type = XmlEntry::DOCTYPE;
681 entry.text = _EMPTY_STRING;
682 _getDocType(p);
683 return;
684 }
685 throw(XmlException(XmlException::EXPECTED_COMMENT_OR_CDATA, _line));
686 }
687 else if (*p == '/')
688 {
689 entry.type = XmlEntry::END_TAG;
690 entry.text = ++p;
691
692 if (!_getElementName(p))
693 throw(XmlException(XmlException::BAD_END_TAG, _line));
694 mike 1.10
695 return;
696 }
697 else if (isalpha(*p) || *p == '_')
698 {
699 entry.type = XmlEntry::START_TAG;
700 entry.text = p;
701
702 Boolean openCloseElement = false;
703
704 if (_getOpenElementName(p, openCloseElement))
705 {
706 if (openCloseElement)
707 entry.type = XmlEntry::EMPTY_TAG;
708 return;
709 }
710 }
711 else
712 throw XmlException(XmlException::BAD_START_TAG, _line);
713
714 //--------------------------------------------------------------------------
715 mike 1.10 // Grab all the attributes:
716 //--------------------------------------------------------------------------
717
718 for (;;)
719 {
720 if (entry.type == XmlEntry::XML_DECLARATION)
721 {
722 if (p[0] == '?' && p[1] == '>')
723 {
724 p += 2;
725 return;
726 }
727 }
728 else if (entry.type == XmlEntry::START_TAG && p[0] == '/' && p[1] =='>')
729 {
730 entry.type = XmlEntry::EMPTY_TAG;
731 p += 2;
732 return;
733 }
734 else if (*p == '>')
735 {
736 mike 1.10 p++;
737 return;
738 }
739
740 XmlAttribute attr;
741 attr.name = p;
742 _getAttributeNameAndEqual(p);
743
744 if (*p != '"' && *p != '\'')
745 throw XmlException(XmlException::BAD_ATTRIBUTE_VALUE, _line);
746
747 attr.value = p + 1;
748 _getAttributeValue(p);
749
750 if (entry.type == XmlEntry::XML_DECLARATION)
751 {
752 // The next thing must a space or a "?>":
753
754 if (!(p[0] == '?' && p[1] == '>') && !isspace(*p))
755 {
756 throw XmlException(
757 mike 1.10 XmlException::BAD_ATTRIBUTE_VALUE, _line);
758 }
759 }
760 else if (!(*p == '>' || (p[0] == '/' && p[1] == '>') || isspace(*p)))
761 {
762 // The next thing must be a space or a '>':
763
764 throw XmlException(XmlException::BAD_ATTRIBUTE_VALUE, _line);
765 }
766
767 _skipWhitespace(p);
768
769 if (entry.attributeCount == XmlEntry::MAX_ATTRIBUTES)
770 throw XmlException(XmlException::TOO_MANY_ATTRIBUTES, _line);
771
772 _substituteReferences((char*)attr.value);
773 entry.attributes[entry.attributeCount++] = attr;
774 }
775 }
776
777 static const char* _typeStrings[] =
778 mike 1.10 {
779 "XML_DECLARATION",
780 "START_TAG",
781 "EMPTY_TAG",
782 "END_TAG",
783 "COMMENT",
784 "CDATA",
785 "DOCTYPE",
786 "CONTENT"
787 };
788
789 void XmlEntry::print() const
790 {
791 PEGASUS_STD(cout) << "=== " << _typeStrings[type] << " ";
792
793 Boolean needQuotes = type == XmlEntry::CDATA || type == XmlEntry::CONTENT;
794
795 if (needQuotes)
796 PEGASUS_STD(cout) << "\"";
797
798 _printValue(text);
799 mike 1.10
800 if (needQuotes)
801 PEGASUS_STD(cout) << "\"";
802
803 PEGASUS_STD(cout) << '\n';
804
805 for (Uint32 i = 0; i < attributeCount; i++)
806 {
807 PEGASUS_STD(cout) << " " << attributes[i].name << "=\"";
808 _printValue(attributes[i].value);
809 PEGASUS_STD(cout) << "\"" << PEGASUS_STD(endl);
810 }
811 }
812
813 const XmlAttribute* XmlEntry::findAttribute(
814 const char* name) const
815 {
816 for (Uint32 i = 0; i < attributeCount; i++)
817 {
818 if (strcmp(attributes[i].name, name) == 0)
819 return &attributes[i];
820 mike 1.10 }
821
822 return 0;
823 }
824
825 // Find first non-whitespace character (set first) and last non-whitespace
826 // character (set last one past this). For example, consider this string:
827 //
828 // " 87 "
829 //
830 // The first pointer would point to '8' and the last pointer woudl point one
831 // beyond '7'.
832
833 static void _findEnds(
834 const char* str,
835 const char*& first,
836 const char*& last)
837 {
838 first = str;
839
840 while (isspace(*first))
841 mike 1.10 first++;
842
843 if (!*first)
844 {
845 last = first;
846 return;
847 }
848
849 last = first + strlen(first);
850
851 while (last != first && isspace(last[-1]))
852 last--;
853 }
854
855 Boolean XmlEntry::getAttributeValue(
856 const char* name,
857 Uint32& value) const
858 {
859 const XmlAttribute* attr = findAttribute(name);
860
861 if (!attr)
862 mike 1.10 return false;
863
864 const char* first;
865 const char* last;
866 _findEnds(attr->value, first, last);
867
868 char* end = 0;
869 long tmp = strtol(first, &end, 10);
870
871 if (!end || end != last)
872 return false;
873
874 value = Uint32(tmp);
875 return true;
876 }
877
878 Boolean XmlEntry::getAttributeValue(
879 const char* name,
880 Real32& value) const
881 {
882 const XmlAttribute* attr = findAttribute(name);
883 mike 1.10
884 if (!attr)
885 return false;
886
887 const char* first;
888 const char* last;
889 _findEnds(attr->value, first, last);
890
891 char* end = 0;
892 double tmp = strtod(first, &end);
893
894 if (!end || end != last)
895 return false;
896
897 value = Uint32(tmp);
898 return true;
899 }
900
901 Boolean XmlEntry::getAttributeValue(
902 const char* name,
903 const char*& value) const
904 mike 1.10 {
905 const XmlAttribute* attr = findAttribute(name);
906
907 if (!attr)
908 return false;
909
910 value = attr->value;
911 return true;
912 }
913
914 Boolean XmlEntry::getAttributeValue(const char* name, String& value) const
915 {
916 const char* tmp;
917
918 if (!getAttributeValue(name, tmp))
919 return false;
920
921 value = tmp;
922 return true;
923 }
924
925 mike 1.10 void XmlAppendCString(Array<Sint8>& out, const char* str)
926 {
927 out.append(str, strlen(str));
928 }
929
930 PEGASUS_NAMESPACE_END
|