Почему gSOAP устанавливает режим stdin в двоичный режим, если читает данные из файлового потока? - PullRequest
6 голосов
/ 08 февраля 2012

Я играл с привязкой данных gSOAP XML, загружая документ XML в класс C ++, модифицируя данные и сериализовав их обратно в XML.

Вот фрагмент кода 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>

Следующий код загружает XML в объект, модифицирует объект и сериализует его обратно в XML. Обратите внимание, что XML загружается из файла через поток файлов, а данные, которые нужно добавить, получают от пользователя через 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;
}

После запуска этого тестового приложения все в порядке, за исключением того, что у всех вновь добавленных элементов есть строки, заканчивающиеся символом возврата каретки (CR - &#xD;):

Модифицированный 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:Book isbn="12345678&#xD;" author="Scott Meyers&#xD;" title="Effective C++&#xD;">
            <gt:CopiesAvailable>123</gt:CopiesAvailable>
        </gt:Book>
    </gt:Books>
    ...
</gt:Library>

Я проследил источник ошибки и обнаружил следующее:

soap_read__gt__Library() вызывает soap_begin_send(), которая выполняет следующую строку:

_setmode(soap->recvfd, _O_BINARY);

soap->recvfd установлен на 0 в soap_init(), а 0 представляет собой значение дескриптора файла stdin.

Как только режим stdin изменяется на двоичный, библиотека STL не разбирает \r\n на один \n для операций чтения, а getline(cin, str), как обычно, читает все до \n, копируя \r в выходную строку. И это именно символ возврата каретки, который появляется в новых строках в окончательном XML.

Мой вопрос: Почему gSOAP изменяет режим stdin, если источником данных является файловый поток? Это ошибка в gSOAP?

ПРИМЕЧАНИЕ:

Как и ожидалось, если режим stdio возвращается к _O_TEXT после soap_begin_send(), но до чтения данных из std::cin, getline() работает нормально. Вот патч:

_setmode(_fileno(stdin), _O_TEXT)

Ответы [ 2 ]

1 голос
/ 09 февраля 2012

Также это вызывает нежелательные побочные эффекты! Используя

_setmode (soap-> recvfd, _O_BINARY); // найдено в stdsoap2.cpp

вы всегда предполагаете, что читаете из файла или стандартного ввода, однако, если вы установили soap-> is в std :: istringstream, это не так.

Просто предположим, что вы используете getchar () в своей основной подпрограмме, но в потоке вы пытаетесь десериализовать xml из std: istringstream (используя привязку gsoap xml). В результате ваша программа будет зависать. единственный способ - установить soap-> recvfd = -1;

С уважением, Ральф

1 голос
/ 08 февраля 2012

Это позволяет избежать проблем с кодированием при чтении и записи XML-кода в кодировке Unicode и UTF-8.

...