1 // ----------------------------------------------------------------------------
2 //
3 // flxmlrpc Copyright (c) 2015 by W1HKJ, Dave Freese <iam_w1hkj@w1hkj.com>
4 //
5 // XmlRpc++ Copyright (c) 2002-2008 by Chris Morley
6 //
7 // This file is part of fldigi
8 //
9 // flxmlrpc is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU Lesser General Public License as published by
11 // the Free Software Foundation; either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 // ----------------------------------------------------------------------------
17 
18 #include <config.h>
19 
20 #include "XmlRpcValue.h"
21 #include "XmlRpcException.h"
22 #include "XmlRpcUtil.h"
23 #include "XmlRpcBase64.h"
24 
25 #include <iostream>
26 #include <ostream>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 
31 namespace XmlRpc {
32 
33 
34   static const char VALUE_TAG[]     = "value";
35   static const char NIL_TAG[]       = "nil";
36   static const char BOOLEAN_TAG[]   = "boolean";
37   static const char DOUBLE_TAG[]    = "double";
38   static const char INT_TAG[]       = "int";
39   static const char I4_TAG[]        = "i4";
40   static const char STRING_TAG[]    = "string";
41   static const char DATETIME_TAG[]  = "dateTime.iso8601";
42   static const char BASE64_TAG[]    = "base64";
43 
44   static const char ARRAY_TAG[]     = "array";
45   static const char DATA_TAG[]      = "data";
46 
47   static const char STRUCT_TAG[]    = "struct";
48   static const char MEMBER_TAG[]    = "member";
49   static const char NAME_TAG[]      = "name";
50 
51 
52   // Format strings
53   std::string XmlRpcValue::_doubleFormat("%f");
54 
55 
56 
57   // Clean up
invalidate()58   void XmlRpcValue::invalidate()
59   {
60     switch (_type) {
61       case TypeString:    delete _value.asString; break;
62       case TypeDateTime:  delete _value.asTime;   break;
63       case TypeBase64:    delete _value.asBinary; break;
64       case TypeArray:     delete _value.asArray;  break;
65       case TypeStruct:    delete _value.asStruct; break;
66       default: break;
67     }
68     _type = TypeInvalid;
69     _value.asBinary = 0;
70   }
71 
72 
73   // Type checking
assertType(Type t) const74   void XmlRpcValue::assertType(Type t) const
75   {
76     if (_type != t)
77     {
78       throw XmlRpcException("type error");
79     }
80   }
81 
assertType(Type t)82   void XmlRpcValue::assertType(Type t)
83   {
84     if (_type == TypeInvalid)
85     {
86       _type = t;
87       switch (_type) {    // Ensure there is a valid value for the type
88         case TypeString:   _value.asString = new std::string(); break;
89         case TypeDateTime: _value.asTime = new struct tm();     break;
90         case TypeBase64:   _value.asBinary = new BinaryData();  break;
91         case TypeArray:    _value.asArray = new ValueArray();   break;
92         case TypeStruct:   _value.asStruct = new ValueStruct(); break;
93         default:           _value.asBinary = 0; break;
94       }
95     }
96     else if (_type != t)
97     {
98       throw XmlRpcException("type error");
99     }
100   }
101 
assertArray(int size) const102   void XmlRpcValue::assertArray(int size) const
103   {
104     if (_type != TypeArray)
105       throw XmlRpcException("type error: expected an array");
106     else if (int(_value.asArray->size()) < size)
107       throw XmlRpcException("range error: array index too large");
108   }
109 
110 
assertArray(int size)111   void XmlRpcValue::assertArray(int size)
112   {
113     if (_type == TypeInvalid) {
114       _type = TypeArray;
115       _value.asArray = new ValueArray(size);
116     } else if (_type == TypeArray) {
117       if (int(_value.asArray->size()) < size)
118         _value.asArray->resize(size);
119     } else
120       throw XmlRpcException("type error: expected an array");
121   }
122 
assertStruct()123   void XmlRpcValue::assertStruct()
124   {
125     if (_type == TypeInvalid) {
126       _type = TypeStruct;
127       _value.asStruct = new ValueStruct();
128     } else if (_type != TypeStruct)
129       throw XmlRpcException("type error: expected a struct");
130   }
131 
132 
133   // Operators
operator =(XmlRpcValue const & rhs)134   XmlRpcValue& XmlRpcValue::operator=(XmlRpcValue const& rhs)
135   {
136     if (this != &rhs)
137     {
138       invalidate();
139       _type = rhs._type;
140       switch (_type) {
141         case TypeBoolean:  _value.asBool = rhs._value.asBool; break;
142         case TypeInt:      _value.asInt = rhs._value.asInt; break;
143         case TypeDouble:   _value.asDouble = rhs._value.asDouble; break;
144         case TypeDateTime: _value.asTime = new struct tm(*rhs._value.asTime); break;
145         case TypeString:   _value.asString = new std::string(*rhs._value.asString); break;
146         case TypeBase64:   _value.asBinary = new BinaryData(*rhs._value.asBinary); break;
147         case TypeArray:    _value.asArray = new ValueArray(*rhs._value.asArray); break;
148         case TypeStruct:   _value.asStruct = new ValueStruct(*rhs._value.asStruct); break;
149         default:           _value.asBinary = 0; break;
150       }
151     }
152     return *this;
153   }
154 
155 
156   // Predicate for tm equality
tmEq(struct tm const & t1,struct tm const & t2)157   static bool tmEq(struct tm const& t1, struct tm const& t2) {
158     return t1.tm_sec == t2.tm_sec && t1.tm_min == t2.tm_min &&
159             t1.tm_hour == t2.tm_hour && t1.tm_mday == t2.tm_mday &&
160             t1.tm_mon == t2.tm_mon && t1.tm_year == t2.tm_year;
161   }
162 
operator ==(XmlRpcValue const & other) const163   bool XmlRpcValue::operator==(XmlRpcValue const& other) const
164   {
165     if (_type != other._type)
166       return false;
167 
168     switch (_type) {
169       case TypeBoolean:  return ( !_value.asBool && !other._value.asBool) ||
170                                 ( _value.asBool && other._value.asBool);
171       case TypeInt:      return _value.asInt == other._value.asInt;
172       case TypeDouble:   return _value.asDouble == other._value.asDouble;
173       case TypeDateTime: return tmEq(*_value.asTime, *other._value.asTime);
174       case TypeString:   return *_value.asString == *other._value.asString;
175       case TypeBase64:   return *_value.asBinary == *other._value.asBinary;
176       case TypeArray:    return *_value.asArray == *other._value.asArray;
177 
178       // The map<>::operator== requires the definition of value< for kcc
179       case TypeStruct:   //return *_value.asStruct == *other._value.asStruct;
180         {
181           if (_value.asStruct->size() != other._value.asStruct->size())
182             return false;
183 
184           ValueStruct::const_iterator it1=_value.asStruct->begin();
185           ValueStruct::const_iterator it2=other._value.asStruct->begin();
186           while (it1 != _value.asStruct->end()) {
187             const XmlRpcValue& v1 = it1->second;
188             const XmlRpcValue& v2 = it2->second;
189             if ( ! (v1 == v2))
190               return false;
191             it1++;
192             it2++;
193           }
194           return true;
195         }
196       default: break;
197     }
198     return true;    // Both invalid values ...
199   }
200 
operator !=(XmlRpcValue const & other) const201   bool XmlRpcValue::operator!=(XmlRpcValue const& other) const
202   {
203     return !(*this == other);
204   }
205 
206 
207   // Works for strings, binary data, arrays, and structs.
size() const208   int XmlRpcValue::size() const
209   {
210     switch (_type) {
211       case TypeString: return int(_value.asString->size());
212       case TypeBase64: return int(_value.asBinary->size());
213       case TypeArray:  return int(_value.asArray->size());
214       case TypeStruct: return int(_value.asStruct->size());
215       default: break;
216     }
217 
218     throw XmlRpcException("type error");
219   }
220 
221   // Checks for existence of struct member
hasMember(const std::string & name) const222   bool XmlRpcValue::hasMember(const std::string& name) const
223   {
224     return _type == TypeStruct && _value.asStruct->find(name) != _value.asStruct->end();
225   }
226 
227 
228   // Set the value from xml. The chars at *offset into valueXml
229   // should be the start of a <value> tag. Destroys any existing value.
fromXml(std::string const & valueXml,int * offset)230   bool XmlRpcValue::fromXml(std::string const& valueXml, int* offset)
231   {
232     int savedOffset = *offset;
233 
234     invalidate();
235     bool emptyTag;
236     if ( ! XmlRpcUtil::nextTagIs(VALUE_TAG, valueXml, offset, &emptyTag))
237       return false;       // Not a value, offset not updated
238 
239     // No value? Pretend its an empty string...
240     if (emptyTag)
241     {
242       *this = "";
243       return true;
244     }
245 
246     // No type tag? Assume string
247 //    bool result = true;
248     bool result = false;
249 
250     int valueOffset = *offset;
251 
252     if (XmlRpcUtil::nextTagIsEnd(VALUE_TAG, valueXml, offset))
253     {
254       result = true;
255       return stringFromXml(valueXml, &valueOffset);
256     }
257     else if (XmlRpcUtil::nextTagIs(NIL_TAG, valueXml, offset, &emptyTag))
258     {
259       _type = TypeNil;
260       result = true;
261     }
262     else if (XmlRpcUtil::nextTagIs(BOOLEAN_TAG, valueXml, offset, &emptyTag))
263     {
264       if (emptyTag) {
265         *this = false;
266         result = true;
267       } else
268         result = boolFromXml(valueXml, offset) &&
269                  XmlRpcUtil::nextTagIsEnd(BOOLEAN_TAG, valueXml, offset);
270     }
271     else if (XmlRpcUtil::nextTagIs(I4_TAG, valueXml, offset, &emptyTag))
272     {
273       if (emptyTag) {
274         *this = 0;
275         result = true;
276       } else
277         result = intFromXml(valueXml, offset) &&
278                  XmlRpcUtil::nextTagIsEnd(I4_TAG, valueXml, offset);
279     }
280     else if (XmlRpcUtil::nextTagIs(INT_TAG, valueXml, offset, &emptyTag))
281     {
282       if (emptyTag) {
283         *this = 0;
284         result = true;
285       } else
286         result = intFromXml(valueXml, offset) &&
287                  XmlRpcUtil::nextTagIsEnd(INT_TAG, valueXml, offset);
288     }
289     else if (XmlRpcUtil::nextTagIs(DOUBLE_TAG, valueXml, offset, &emptyTag))
290     {
291       if (emptyTag) {
292         *this = 0.0;
293         result = true;
294       } else
295         result = doubleFromXml(valueXml, offset) &&
296                  XmlRpcUtil::nextTagIsEnd(DOUBLE_TAG, valueXml, offset);
297     }
298     else if (XmlRpcUtil::nextTagIs(STRING_TAG, valueXml, offset, &emptyTag))
299     {
300       if (emptyTag) {
301         *this = "";
302         result = true;
303       } else
304         result = stringFromXml(valueXml, offset) &&
305                  XmlRpcUtil::nextTagIsEnd(STRING_TAG, valueXml, offset);
306     }
307     else if (XmlRpcUtil::nextTagIs(DATETIME_TAG, valueXml, offset, &emptyTag))
308     {
309       if (emptyTag)
310         result = false;
311       else
312         result = timeFromXml(valueXml, offset) &&
313                  XmlRpcUtil::nextTagIsEnd(DATETIME_TAG, valueXml, offset);
314     }
315     else if (XmlRpcUtil::nextTagIs(BASE64_TAG, valueXml, offset, &emptyTag))
316     {
317       if (emptyTag)
318         result = binaryFromXml("", 0);
319       else
320         result = binaryFromXml(valueXml, offset) &&
321                  XmlRpcUtil::nextTagIsEnd(BASE64_TAG, valueXml, offset);
322     }
323     else if (XmlRpcUtil::nextTagIs(ARRAY_TAG, valueXml, offset, &emptyTag))
324     {
325       if (emptyTag)
326         result = false;
327       else
328         result = arrayFromXml(valueXml, offset) &&
329                  XmlRpcUtil::nextTagIsEnd(ARRAY_TAG, valueXml, offset);
330     }
331     else if (XmlRpcUtil::nextTagIs(STRUCT_TAG, valueXml, offset, &emptyTag))
332     {
333       if (emptyTag)
334         result = false;
335       else
336         result = structFromXml(valueXml, offset) &&
337                  XmlRpcUtil::nextTagIsEnd(STRUCT_TAG, valueXml, offset);
338     }
339 
340     // Unrecognized tag after <value> or no </value>
341     if ( ! result || ! XmlRpcUtil::nextTagIsEnd(VALUE_TAG, valueXml, offset))
342     {
343       *offset = savedOffset;
344       return false;
345     }
346 
347     return true;
348   }
349 
350   // Encode the Value in xml
toXml() const351   std::string XmlRpcValue::toXml() const
352   {
353     switch (_type) {
354       case TypeNil:      return nilToXml();
355       case TypeBoolean:  return boolToXml();
356       case TypeInt:      return intToXml();
357       case TypeDouble:   return doubleToXml();
358       case TypeString:   return stringToXml();
359       case TypeDateTime: return timeToXml();
360       case TypeBase64:   return binaryToXml();
361       case TypeArray:    return arrayToXml();
362       case TypeStruct:   return structToXml();
363       default: break;
364     }
365     return std::string();   // Invalid value
366   }
367 
368 
369   // Boolean
boolFromXml(std::string const & valueXml,int * offset)370   bool XmlRpcValue::boolFromXml(std::string const& valueXml, int* offset)
371   {
372     const char* valueStart = valueXml.c_str() + *offset;
373     char* valueEnd;
374     long ivalue = strtol(valueStart, &valueEnd, 10);
375     if (valueEnd == valueStart || (ivalue != 0 && ivalue != 1))
376       return false;
377 
378     _type = TypeBoolean;
379     _value.asBool = (ivalue == 1);
380     *offset += int(valueEnd - valueStart);
381     return true;
382   }
383 
nilToXml() const384   std::string XmlRpcValue::nilToXml() const
385   {
386     return "<value><nil/></value>";
387   }
388 
boolToXml() const389   std::string XmlRpcValue::boolToXml() const
390   {
391     static std::string booleanTrueXml("<value><boolean>1</boolean></value>");
392     static std::string booleanFalseXml("<value><boolean>0</boolean></value>");
393     return _value.asBool ? booleanTrueXml : booleanFalseXml;
394   }
395 
396   // Int
intFromXml(std::string const & valueXml,int * offset)397   bool XmlRpcValue::intFromXml(std::string const& valueXml, int* offset)
398   {
399     const char* valueStart = valueXml.c_str() + *offset;
400     char* valueEnd;
401     long ivalue = strtol(valueStart, &valueEnd, 10);
402     if (valueEnd == valueStart)
403       return false;
404 
405     _type = TypeInt;
406     _value.asInt = int(ivalue);
407     *offset += int(valueEnd - valueStart);
408     return true;
409   }
410 
intToXml() const411   std::string XmlRpcValue::intToXml() const
412   {
413     char buf[256];
414     snprintf(buf, sizeof(buf)-1, "<value><i4>%d</i4></value>", _value.asInt);
415     buf[sizeof(buf)-1] = 0;
416 
417     return std::string(buf);
418   }
419 
420   // Double
doubleFromXml(std::string const & valueXml,int * offset)421   bool XmlRpcValue::doubleFromXml(std::string const& valueXml, int* offset)
422   {
423     const char* valueStart = valueXml.c_str() + *offset;
424     char* valueEnd;
425     double dvalue = strtod(valueStart, &valueEnd);
426     if (valueEnd == valueStart)
427       return false;
428 
429     _type = TypeDouble;
430     _value.asDouble = dvalue;
431     *offset += int(valueEnd - valueStart);
432     return true;
433   }
434 
doubleToXml() const435   std::string XmlRpcValue::doubleToXml() const
436   {
437     char fmtbuf[256], buf[256];
438     snprintf(fmtbuf, sizeof(fmtbuf)-1, "<value><double>%s</double></value>", getDoubleFormat().c_str());
439     fmtbuf[sizeof(fmtbuf)-1] = 0;
440     snprintf(buf, sizeof(buf)-1, fmtbuf, _value.asDouble);
441     buf[sizeof(buf)-1] = 0;
442 
443     return std::string(buf);
444   }
445 
446   // String
stringFromXml(std::string const & valueXml,int * offset)447   bool XmlRpcValue::stringFromXml(std::string const& valueXml, int* offset)
448   {
449     size_t valueEnd = valueXml.find('<', *offset);
450     if (valueEnd == std::string::npos)
451       return false;     // No end tag;
452 
453     _type = TypeString;
454     _value.asString = new std::string(XmlRpcUtil::xmlDecode(valueXml.substr(*offset, valueEnd-*offset)));
455     *offset += int(_value.asString->length());
456     return true;
457   }
458 
stringToXml() const459   std::string XmlRpcValue::stringToXml() const
460   {
461     return std::string("<value>") + XmlRpcUtil::xmlEncode(*_value.asString) + std::string("</value>");
462   }
463 
464   // DateTime (stored as a struct tm)
timeFromXml(std::string const & valueXml,int * offset)465   bool XmlRpcValue::timeFromXml(std::string const& valueXml, int* offset)
466   {
467     size_t valueEnd = valueXml.find('<', *offset);
468     if (valueEnd == std::string::npos)
469       return false;     // No end tag;
470 
471     std::string stime = valueXml.substr(*offset, valueEnd-*offset);
472 
473     struct tm t;
474     if (sscanf(stime.c_str(),"%4d%2d%2dT%2d:%2d:%2d",&t.tm_year,&t.tm_mon,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec) != 6)
475       return false;
476 
477     t.tm_isdst = -1;
478     _type = TypeDateTime;
479     _value.asTime = new struct tm(t);
480     *offset += int(stime.length());
481 
482     return true;
483   }
484 
timeToXml() const485   std::string XmlRpcValue::timeToXml() const
486   {
487     struct tm* t = _value.asTime;
488     char buf[50];
489     memset(buf, 0, 50);
490     snprintf(buf, sizeof(buf)-1, "%04d%02d%02dT%02d:%02d:%02d",
491       1900+t->tm_year,1+t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
492 
493     return std::string("<value><dateTime.iso8601>") + buf + std::string("</dateTime.iso8601></value>");
494   }
495 
496 
497   // Base64
binaryFromXml(std::string const & valueXml,int * offset)498   bool XmlRpcValue::binaryFromXml(std::string const& valueXml, int* offset)
499   {
500     size_t valueEnd = valueXml.find('<', *offset);
501     if (valueEnd == std::string::npos)
502       return false;     // No end tag;
503 
504     _type = TypeBase64;
505     std::string asString = valueXml.substr(*offset, valueEnd-*offset);
506     _value.asBinary = new BinaryData();
507     // check whether base64 encodings can contain chars xml encodes...
508 
509     // convert from base64 to binary
510     int iostatus = 0;
511 	  xmlrpc_base64<char> decoder;
512     std::back_insert_iterator<BinaryData> ins = std::back_inserter(*(_value.asBinary));
513 		decoder.get(asString.begin(), asString.end(), ins, iostatus);
514 
515     *offset += int(asString.length());
516     return true;
517   }
518 
519 
binaryToXml() const520   std::string XmlRpcValue::binaryToXml() const
521   {
522     // convert to base64
523     std::vector<char> base64data;
524     int iostatus = 0;
525     xmlrpc_base64<char> encoder;
526     std::back_insert_iterator<std::vector<char> > ins = std::back_inserter(base64data);
527     encoder.put(_value.asBinary->begin(), _value.asBinary->end(), ins, iostatus, xmlrpc_base64<>::crlf());
528 
529     // Wrap with xml
530     std::string xml = "<value><base64>";
531     xml.append(base64data.begin(), base64data.end());
532     xml += "</base64></value>";
533     return xml;
534   }
535 
536 
537   // Array
arrayFromXml(std::string const & valueXml,int * offset)538   bool XmlRpcValue::arrayFromXml(std::string const& valueXml, int* offset)
539   {
540     bool emptyTag;
541     if ( ! XmlRpcUtil::nextTagIs(DATA_TAG, valueXml, offset, &emptyTag))
542       return false;
543 
544     _type = TypeArray;
545     _value.asArray = new ValueArray;
546 
547     if ( ! emptyTag)
548     {
549       XmlRpcValue v;
550       while (v.fromXml(valueXml, offset))
551         _value.asArray->push_back(v);       // copy...
552 
553       // Skip the trailing </data>
554       (void) XmlRpcUtil::nextTagIsEnd(DATA_TAG, valueXml, offset);
555     }
556     return true;
557   }
558 
559 
560   // In general, its preferable to generate the xml of each element of the
561   // array as it is needed rather than glomming up one big string.
arrayToXml() const562   std::string XmlRpcValue::arrayToXml() const
563   {
564     std::string xml = "<value><array><data>";
565 
566     int s = int(_value.asArray->size());
567     for (int i=0; i<s; ++i)
568        xml += _value.asArray->at(i).toXml();
569 
570     xml += "</data></array></value>";
571     return xml;
572   }
573 
574 
575   // Struct
structFromXml(std::string const & valueXml,int * offset)576   bool XmlRpcValue::structFromXml(std::string const& valueXml, int* offset)
577   {
578     _type = TypeStruct;
579     _value.asStruct = new ValueStruct;
580 
581     std::string name;
582     bool emptyTag;
583     while (XmlRpcUtil::nextTagIs(MEMBER_TAG, valueXml, offset, &emptyTag))
584     {
585       if ( ! emptyTag)
586       {
587         if (XmlRpcUtil::parseTag(NAME_TAG, valueXml, offset, name))
588         {
589           // value
590           XmlRpcValue val(valueXml, offset);
591           if ( ! val.valid()) {
592             invalidate();
593             return false;
594           }
595           const std::pair<const std::string, XmlRpcValue> p(name, val);
596           _value.asStruct->insert(p);
597 
598           (void) XmlRpcUtil::nextTagIsEnd(MEMBER_TAG, valueXml, offset);
599         }
600       }
601     }
602 
603     return true;
604   }
605 
606 
607   // In general, its preferable to generate the xml of each element
608   // as it is needed rather than glomming up one big string.
structToXml() const609   std::string XmlRpcValue::structToXml() const
610   {
611     std::string xml = "<value><struct>";
612 
613     ValueStruct::const_iterator it;
614     for (it=_value.asStruct->begin(); it!=_value.asStruct->end(); ++it)
615     {
616       xml += "<member><name>";
617       xml += XmlRpcUtil::xmlEncode(it->first);
618       xml += "</name>";
619       xml += it->second.toXml();
620       xml += "</member>";
621     }
622 
623     xml += "</struct></value>";
624     return xml;
625   }
626 
627 
628 
629   // Write the value without xml encoding it
write(std::ostream & os) const630   std::ostream& XmlRpcValue::write(std::ostream& os) const {
631     switch (_type) {
632       default:           break;
633       case TypeBoolean:  os << _value.asBool; break;
634       case TypeInt:      os << _value.asInt; break;
635       case TypeDouble:   os << _value.asDouble; break;
636       case TypeString:   os << *_value.asString; break;
637       case TypeDateTime:
638         {
639           struct tm* t = _value.asTime;
640           char buf[20];
641           snprintf(buf, sizeof(buf)-1, "%4d%02d%02dT%02d:%02d:%02d",
642             t->tm_year,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
643           buf[sizeof(buf)-1] = 0;
644           os << buf;
645           break;
646         }
647       case TypeBase64:
648         {
649           int iostatus = 0;
650           std::ostreambuf_iterator<char> out(os);
651           xmlrpc_base64<char> encoder;
652           encoder.put(_value.asBinary->begin(), _value.asBinary->end(), out, iostatus, xmlrpc_base64<>::crlf());
653           break;
654         }
655       case TypeArray:
656         {
657           int s = int(_value.asArray->size());
658           os << '{';
659           for (int i=0; i<s; ++i)
660           {
661             if (i > 0) os << ',';
662             _value.asArray->at(i).write(os);
663           }
664           os << '}';
665           break;
666         }
667       case TypeStruct:
668         {
669           os << '[';
670           ValueStruct::const_iterator it;
671           for (it=_value.asStruct->begin(); it!=_value.asStruct->end(); ++it)
672           {
673             if (it!=_value.asStruct->begin()) os << ',';
674             os << it->first << ':';
675             it->second.write(os);
676           }
677           os << ']';
678           break;
679         }
680 
681     }
682 
683     return os;
684   }
685 
686 } // namespace XmlRpc
687 
688 
689 // ostream
operator <<(std::ostream & os,XmlRpc::XmlRpcValue & v)690 std::ostream& operator<<(std::ostream& os, XmlRpc::XmlRpcValue& v)
691 {
692   // If you want to output in xml format:
693   //return os << v.toXml();
694   return v.write(os);
695 }
696 
697