You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ryzom-core/code/nel/src/misc/o_xml.cpp

680 lines
15 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdmisc.h"
#include "nel/misc/o_xml.h"
#ifndef NL_DONT_USE_EXTERNAL_CODE
// Include from libxml2
#include <libxml/xmlerror.h>
using namespace std;
namespace NLMISC
{
// ***************************************************************************
const char SEPARATOR = ' ';
// ***************************************************************************
#define writenumber(src,format,digits) \
char number_as_cstring [digits+1]; \
sprintf( number_as_cstring, format, src ); \
serialSeparatedBufferOut( number_as_cstring );
// ***************************************************************************
// XML callbacks
// ***************************************************************************
int xmlOutputWriteCallbackForNeL ( void *context, const char *buffer, int len)
{
// no need to save empty buffer
if(len == 0) return 0;
// Get the object
COXml *object = (COXml*) context;
// Serialise the buffer
object->_InternalStream->serialBuffer ((uint8*)buffer, len);
// Return the value
return len;
}
// ***************************************************************************
int xmlOutputCloseCallbackForNeL ( void * /* context */ )
{
// Get the object
// COXml *object = (COXml*) context;
// Does nothing
return 1;
}
// ***************************************************************************
xmlDocPtr COXml::getDocument ()
{
if (_Document)
return _Document;
// Initialise the document
_Document = xmlNewDoc ((const xmlChar *)_Version.c_str());
return _Document;
}
// ***************************************************************************
inline void COXml::flushContentString ()
{
// Current node must exist here
nlassert (_CurrentNode);
// String size
uint size=(uint)_ContentString.length();
// Some content to write ?
if (size)
{
// Write it in the current node
xmlNodePtr textNode = xmlNewText ((const xmlChar *)_ContentString.c_str());
xmlAddChild (_CurrentNode, textNode);
// Empty the string
_ContentString.erase ();
}
}
// ***************************************************************************
COXml::COXml () : IStream (false /* Output mode */)
{
// Set XML mode
setXMLMode (true);
// Set the stream
_InternalStream = NULL;
// Set the version
_Version = "1.0";
// Initialise the document
_Document = NULL;
// Current node
_CurrentNode = NULL;
// Content string
_ContentString = "";
// Push begin
_PushBegin = false;
}
// ***************************************************************************
void xmlGenericErrorFuncWrite (void *ctx, const char *msg, ...)
{
// Get the error string
string str;
NLMISC_CONVERT_VARGS (str, msg, NLMISC::MaxCStringSize);
((COXml*)ctx)->_ErrorString += str;
}
// ***************************************************************************
bool COXml::init (IStream *stream, const char *version)
{
resetPtrTable();
// Output stream ?
if (!stream->isReading())
{
// Set error handler
_ErrorString = "";
xmlSetGenericErrorFunc (this, xmlGenericErrorFuncWrite);
// Set XML mode
setXMLMode (true);
// Set the stream
_InternalStream = stream;
// Set the version
_Version = version;
// Initialise the document
_Document = NULL;
// Current node
_CurrentNode = NULL;
// Content string
_ContentString = "";
// Push begin
_PushBegin = false;
// Ok
return true;
}
else
return false;
}
// ***************************************************************************
COXml::~COXml ()
{
// Flush document to the internal stream
flush ();
}
// ***************************************************************************
void COXml::serialSeparatedBufferOut( const char *value )
{
nlassert( ! isReading() );
// Output stream has been setuped ?
if ( _InternalStream )
{
// Current node presents ?
if (_CurrentNode)
{
// Write a push attribute ?
if (_PushBegin)
{
// Current attrib is set ?
if (_AttribPresent)
{
// Set the attribute
xmlSetProp (_CurrentNode, (const xmlChar*)_AttribName.c_str(), (const xmlChar*)value);
// The attribute has been used
_AttribPresent = false;
}
else
{
// * Error, the stream don't use XML streaming properly
// * You must take care of this in your last serial call:
// * - Between xmlPushBegin() and xmlPushEnd(), before each serial, you must set the attribute name with xmlSetAttrib.
// * - Between xmlPushBegin() and xmlPushEnd(), you must serial only basic objects (numbers and strings).
nlerror ( "Error, the stream don't use XML streaming properly" );
}
}
else
{
// Get the content buffer size
uint size=(uint)_ContentString.length();
// Add a separator
if ((size) && (_ContentString[size-1]!='\n'))
_ContentString += SEPARATOR;
// Concat the strings
_ContentString += value;
}
}
else
{
// * Error, no current node present.
// * Check that your serial is initialy made between a xmlPushBegin and xmlPushEnd calls.
nlerror ( "Error, the stream don't use XML streaming properly" );
}
}
else
{
nlerror ( "Output stream has not been setuped" );
}
}
// ***************************************************************************
void COXml::serial(uint8 &b)
{
// Write the number
writenumber( (uint16)b,"%hu", 3 );
}
// ***************************************************************************
void COXml::serial(sint8 &b)
{
writenumber( (sint16)b, "%hd", 4 );
}
// ***************************************************************************
void COXml::serial(uint16 &b)
{
writenumber( b, "%hu", 5 );
}
// ***************************************************************************
void COXml::serial(sint16 &b)
{
writenumber( b, "%hd", 6 );
}
// ***************************************************************************
void COXml::serial(uint32 &b)
{
writenumber( b, "%u", 10 );
}
// ***************************************************************************
void COXml::serial(sint32 &b)
{
writenumber( b, "%d", 11 );
}
// ***************************************************************************
void COXml::serial(uint64 &b)
{
writenumber( b, "%"NL_I64"u", 20 );
}
// ***************************************************************************
void COXml::serial(sint64 &b)
{
writenumber( b, "%"NL_I64"d", 20 );
}
// ***************************************************************************
void COXml::serial(float &b)
{
writenumber( (double)b, "%f", 128 );
}
// ***************************************************************************
void COXml::serial(double &b)
{
writenumber( b, "%f", 128 );
}
// ***************************************************************************
void COXml::serial(bool &b)
{
serialBit(b);
}
// ***************************************************************************
void COXml::serialBit(bool &bit)
{
uint8 u = (uint8)bit;
serial( u );
}
// ***************************************************************************
#ifndef NL_OS_CYGWIN
void COXml::serial(char &b)
{
char tmp[2] = {b , 0};
serialSeparatedBufferOut( tmp );
}
#endif // NL_OS_CYGWIN
// ***************************************************************************
void COXml::serial(std::string &b)
{
nlassert( ! isReading() );
// Attibute ?
if (_PushBegin)
{
// Only serial the string
serialSeparatedBufferOut( b.c_str() );
}
else
{
// Open a string node
xmlPush ("S");
// Serial the string
serialSeparatedBufferOut( b.c_str() );
// Close the node
xmlPop ();
}
}
// ***************************************************************************
void COXml::serial(ucstring &b)
{
nlassert( ! isReading() );
// convert ucstring to utf-8 std::string
std::string output = b.toUtf8();
// Serial this string
serial (output);
}
// ***************************************************************************
void COXml::serialBuffer(uint8 *buf, uint len)
{
// Open a node
xmlPush ("BUFFER");
// Serialize the buffer
for (uint i=0; i<len; i++)
{
xmlPush ("ELM");
serial (buf[i]);
xmlPop ();
}
// Close the node
xmlPop ();
}
// ***************************************************************************
bool COXml::xmlPushBeginInternal (const char *nodeName)
{
nlassert( ! isReading() );
// Check _InternalStream
if ( _InternalStream )
{
// Can make a xmlPushBegin ?
if ( ! _PushBegin )
{
// Current node exist ?
if (_CurrentNode==NULL)
{
// No document ?
if (_Document == NULL)
{
// Initialise the document
_Document = xmlNewDoc ((const xmlChar *)_Version.c_str());
// Return NULL if error
nlassert (_Document);
}
// Create the first node
_CurrentNode=xmlNewDocNode (_Document, NULL, (const xmlChar*)nodeName, NULL);
xmlDocSetRootElement (_Document, _CurrentNode);
// Return NULL if error
nlassert (_CurrentNode);
}
else
{
// Flush current content string ?
flushContentString ();
// Create a new node
_CurrentNode=xmlNewChild (_CurrentNode, NULL, (const xmlChar*)nodeName, NULL);
// Return NULL if error
nlassert (_CurrentNode);
}
// Push begun
_PushBegin = true;
}
else
{
nlwarning ( "XML: You must close your xmlPushBegin - xmlPushEnd before calling a new xmlPushBegin.");
return false;
}
}
else
{
nlwarning ( "XML: Output stream has not been setuped.");
return false;
}
// Ok
return true;
}
// ***************************************************************************
bool COXml::xmlPushEndInternal ()
{
nlassert( ! isReading() );
// Check _InternalStream
if ( _InternalStream )
{
// Can make a xmlPushEnd ?
if ( _PushBegin )
{
// Push begun
_PushBegin = false;
}
else
{
nlwarning ( "XML: You must call xmlPushBegin before calling xmlPushEnd.");
return false;
}
}
else
{
nlwarning ( "XML: Output stream has not been setuped.");
return false;
}
// Ok
return true;
}
// ***************************************************************************
bool COXml::xmlPopInternal ()
{
nlassert( ! isReading() );
// Check _InternalStream
if ( _InternalStream )
{
// Not in the push mode ?
if ( ! _PushBegin )
{
// Some content to write ?
flushContentString ();
// Get parent
_CurrentNode=_CurrentNode->parent;
}
else
{
nlwarning ( "XML: You must call xmlPop after xmlPushEnd.");
return false;
}
}
else
{
nlwarning ( "XML: Output stream has not been setuped.");
return false;
}
// Ok
return true;
}
// ***************************************************************************
bool COXml::xmlSetAttribInternal (const char *attribName)
{
nlassert( ! isReading() );
// Check _InternalStream
if ( _InternalStream )
{
// Can make a xmlPushEnd ?
if ( _PushBegin )
{
// Set attribute name
_AttribName = attribName;
// Attribute name is present
_AttribPresent = true;
}
else
{
nlwarning ( "XML: You must call xmlSetAttrib between xmlPushBegin and xmlPushEnd calls.");
return false;
}
}
else
{
nlwarning ( "XML: Output stream has not been setuped.");
return false;
}
// Ok
return true;
}
// ***************************************************************************
bool COXml::xmlBreakLineInternal ()
{
nlassert( ! isReading() );
// Check _InternalStream
if ( _InternalStream )
{
// Not in the push mode ?
if ( ! _PushBegin )
{
// Add a break line
_ContentString += '\n';
}
else
{
nlwarning ( "XML: You must call xmlNBreakLine after xmlPushEnd.");
return false;
}
}
else
{
nlwarning ( "XML: Output stream has not been setuped.");
return false;
}
// Ok
return true;
}
// ***************************************************************************
bool COXml::xmlCommentInternal (const char *comment)
{
nlassert( ! isReading() );
// Check _InternalStream
if ( _InternalStream )
{
// Not in the push mode ?
if ( _CurrentNode != NULL)
{
// Add a comment node
xmlNodePtr commentPtr = xmlNewComment ((const xmlChar *)comment);
// Add the node
xmlAddChild (_CurrentNode, commentPtr);
}
else
{
nlwarning ( "XML: You must call xmlCommentInternal between xmlPushBegin and xmlPushEnd.");
return false;
}
}
else
{
nlwarning ( "XML: Output stream has not been setuped.");
return false;
}
// Ok
return true;
}
// ***************************************************************************
void COXml::flush ()
{
if (_Document)
{
// Generate indentation
xmlKeepBlanksDefault (0);
// Create a output context
xmlOutputBufferPtr outputBuffer = xmlOutputBufferCreateIO ( xmlOutputWriteCallbackForNeL, xmlOutputCloseCallbackForNeL, this, NULL );
// Save the file
int res = xmlSaveFormatFileTo (outputBuffer, _Document, NULL, 1);
// No error should be returned because, exception should be raised by the internal stream
nlassert (res!=-1);
// Free the document
xmlFreeDoc (_Document);
_Document = NULL;
}
}
// ***************************************************************************
bool COXml::isStringValidForProperties (const char *str)
{
while (*str)
{
if (*str == '\n')
return false;
str++;
}
return true;
}
// ***************************************************************************
const char *COXml::getErrorString () const
{
return _ErrorString.c_str ();
}
} // NLMISC
#endif // NL_DONT_USE_EXTERNAL_CODE