I've been playing with gSOAP XML data binding by loading XML document into C++ class, modifying data and serializing it back into XML.
Here's the snippet of the XML - library.xml:
<?xml version="1.0" encoding="UTF-8"?>
<gt:Library xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gt="http://www.bk.com/gSOAP/test">
<gt:Books>
<gt:Book isbn="0132350882" author="Robert C. Martin" title="Clean Code">
<gt:CopiesAvailable>2</gt:CopiesAvailable>
</gt:Book>
<gt:Book isbn="020161622X" author="Andrew Hunt" title="The Pragmatic Programmer">
<gt:CopiesAvailable>0</gt:CopiesAvailable>
</gt:Book>
<gt:Book isbn="0201633612" author="Erich Gamma" title="Design patterns">
<gt:CopiesAvailable>1</gt:CopiesAvailable>
</gt:Book>
</gt:Books>
...
</gt:Library>
The following code loads XML into object, modifies object and serializes it back into XML. Note that XML is loaded from a file, via file stream and that data to be added is obtained from a user, via stdin (cin).
main.cpp:
#include "soapH.h"
#include "gt.nsmap"
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::fstream;
using std::string;
using std::stringstream;
void DisplayAllBooks(const _gt__Library& library)
{
cout << "\n\nDisplaying all books in the library:" << endl;
std::vector<_gt__Library_Books_Book>::const_iterator it = library.Books.Book.begin();
for(;it != library.Books.Book.end(); it++)
{
cout << "\nBook:\n" << "\tTitle:" << (*it).title << "\n\tAuthor:" << (*it).author <<"\n\tISBN: " << (*it).isbn << "\n\tCopies available: " << static_cast<int>((*it).CopiesAvailable) << endl;
}
}
void AddBook(_gt__Library& library)
{
cout << "\n\nAdding a new book:" << endl;
_gt__Library_Books_Book book;
cout << "\tTitle: " << std::flush;
getline(cin, book.title);
cout << "\tAuthor: " << std::flush;
getline(cin, book.author);
cout << "\tISBN:" << std::flush;
getline(cin, book.isbn);
cout << "\tCopies available: " << std::flush;
string strCopiesAvailable;
getline(cin, strCopiesAvailable);
stringstream ss(strCopiesAvailable);
ss >> book.CopiesAvailable;
library.Books.Book.push_back(book);
}
// Terminate and destroy soap
void DestroySoap(struct soap* pSoap)
{
// remove deserialized class instances (C++ objects)
soap_destroy(pSoap);
// clean up and remove deserialized data
soap_end(pSoap);
// detach context (last use and no longer in scope)
soap_done(pSoap);
}
int main()
{
//
// Create and intialize soap
//
// gSOAP runtime context
struct soap soap;
// initialize runtime context
soap_init(&soap);
// Set input mode
soap_imode(&soap, SOAP_ENC_XML);
// reset deserializers; start new (de)serialization phase
soap_begin(&soap);
//
// Load XML (Deserialize)
//
_gt__Library library;
string strXML = "library.xml";
ifstream fstreamIN(strXML);
soap.is = &fstreamIN;
// calls soap_begin_recv, soap_get__gt__Library and soap_end_recv
if(soap_read__gt__Library(&soap, &library) != SOAP_OK)
{
std::cout << "soap_read__gt__Library() failed" << std::endl;
DestroySoap(&soap);
return 1;
}
fstreamIN.close();
//
// Display books before and after adding a new book
//
DisplayAllBooks(library);
AddBook(library);
DisplayAllBooks(library);
//
// Serialize
//
soap_set_omode(&soap, SOAP_XML_INDENT);
ofstream fstreamOUT("library.xml");
soap.os = &fstreamOUT;
// calls soap_begin_send, soap_serialize, soap_put and soap_end_send
if(soap_write__gt__Library(&soap, &library) != SOAP_OK)
{
std::cout << "soap_write__gt__Library() failed" << std::endl;
DestroySoap(&soap);
return 1;
}
fstreamOUT.close();
DestroySoap(&soap);
return 0;
}
After running this test application, everything is fine apart from all newly added elements have strings that terminate with a Carriage Return character (CR - 
):
Modified XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<gt:Library xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gt="http://www.bk.com/gSOAP/test">
<gt:Books>
<gt:Book isbn="0132350882" author="Robert C. Martin" title="Clean Code">
<gt:CopiesAvailable>2</gt:CopiesAvailable>
</gt:Book>
<gt:Book isbn="020161622X" author="Andrew Hunt" title="The Pragmatic Programmer">
<gt:CopiesAvailable>0</gt:CopiesAvailable>
</gt:Book>
<gt:Book isbn="0201633612" author="Erich Gamma" title="Design patterns">
<gt:CopiesAvailable>1</gt:CopiesAvailable>
</gt:Book>
<gt:Book isbn="12345678
" author="Scott Meyers
" title="Effective C++
">
<gt:CopiesAvailable>123</gt:CopiesAvailable>
</gt:Book>
</gt:Books>
...
</gt:Library>
I traced the source of the bug and found the following:
soap_read__gt__Library()
calls soap_begin_send()
which executes the following line:
_setmode(soap->recvfd, _O_BINARY);
soap->recvfd
is set to 0
in soap_init()
and 0
is a value of file descriptor of stdin
.
Once stdin
's mode is changed to a binary, STL library does not parse \r\n
to a single \n
for read operations and getline(cin, str)
, as usual, reads everything up to \n
, copying \r
into output string. And that is exactly Carriage Return character which appears in new strings in the final XML.
My question is: Why does gSOAP modify stdin
mode if the data source is a file stream? Is this a bug in gSOAP?
NOTE:
As expected, if stdio
's mode is reverted back to _O_TEXT
after soap_begin_send()
but before reading data from std::cin
, getline()
works fine. Here is the patch:
_setmode(_fileno(stdin), _O_TEXT)
soap->is->read()
is executed), not from astdio
. gSOAP leavesstdio
in the binary mode aftersoap_begin_send()
and never reverts its back to the default, text mode. – Neoteric