I played with gSOAP XML data binding by loading an XML document into a C ++ class, modifying the data and serializing it back into XML.
Here is a snippet of 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 the XML into an object, modifies the object, and converts it to XML. Note that XML is loaded from the file through the file stream and the data that needs to be added is received from the 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 starting this test application, everything is fine, except for all the added elements, there are lines that end with a carriage return (CR - 
):
The modified XML is as follows:
<?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 error and found the following:
soap_read__gt__Library()
calls soap_begin_send()
, which executes the following line:
_setmode(soap->recvfd, _O_BINARY);
soap->recvfd
set to 0
in soap_init()
, and 0
is the value of the file descriptor stdin
.
Once the stdin
mode is changed to binary, the STL library does not parse \r\n
by one \n
for read operations and getline(cin, str)
, as usual, reads everything up to \n
, copying \r
> to the output line. And this is exactly the carriage return character that appears on new lines in the final XML.
My question is: Why does gSOAP change stdin
mode if the data source is a file stream? Is this a bug in gSOAP?
NOTE:
As expected, if stdio
mode returns 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)