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