|
|
//====== Copyright � 1996-2004, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#include "dmserializerkeyvalues2.h"
#include <ctype.h>
#include "datamodel/idatamodel.h"
#include "datamodel.h"
#include "datamodel/dmelement.h"
#include "datamodel/dmattributevar.h"
#include "dmattributeinternal.h"
#include "dmelementdictionary.h"
#include "DmElementFramework.h"
#include "tier1/utlbuffer.h"
#include <limits.h>
//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
class CUtlBuffer;
//-----------------------------------------------------------------------------
// a simple class to keep track of a stack of valid parsed symbols
//-----------------------------------------------------------------------------
class CKeyValues2ErrorStack { public: CKeyValues2ErrorStack();
// Sets the filename to report with errors; sets the line number to 0
void SetFilename( const char *pFilename );
// Current line control
void IncrementCurrentLine(); void SetCurrentLine( int nLine ); int GetCurrentLine() const;
// entering a new keyvalues block, save state for errors
// Not save symbols instead of pointers because the pointers can move!
int Push( CUtlSymbolLarge symName );
// exiting block, error isn't in this block, remove.
void Pop();
// Allows you to keep the same stack level, but change the name as you parse peers
void Reset( int stackLevel, CUtlSymbolLarge symName );
// Hit an error, report it and the parsing stack for context
void ReportError( const char *pError, ... );
private: enum { MAX_ERROR_STACK = 64 };
CUtlSymbolLarge m_errorStack[MAX_ERROR_STACK]; const char *m_pFilename; int m_nFileLine; int m_errorIndex; int m_maxErrorIndex; };
//-----------------------------------------------------------------------------
// Singleton instance
//-----------------------------------------------------------------------------
static CKeyValues2ErrorStack g_KeyValues2ErrorStack;
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CKeyValues2ErrorStack::CKeyValues2ErrorStack() : m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0), m_nFileLine(1) { }
//-----------------------------------------------------------------------------
// Sets the filename
//-----------------------------------------------------------------------------
void CKeyValues2ErrorStack::SetFilename( const char *pFilename ) { m_pFilename = pFilename; m_maxErrorIndex = 0; m_nFileLine = 1; }
//-----------------------------------------------------------------------------
// Current line control
//-----------------------------------------------------------------------------
void CKeyValues2ErrorStack::IncrementCurrentLine() { ++m_nFileLine; }
void CKeyValues2ErrorStack::SetCurrentLine( int nLine ) { m_nFileLine = nLine; }
int CKeyValues2ErrorStack::GetCurrentLine() const { return m_nFileLine; }
//-----------------------------------------------------------------------------
// entering a new keyvalues block, save state for errors
// Not save symbols instead of pointers because the pointers can move!
//-----------------------------------------------------------------------------
int CKeyValues2ErrorStack::Push( CUtlSymbolLarge symName ) { if ( m_errorIndex < MAX_ERROR_STACK ) { m_errorStack[m_errorIndex] = symName; } m_errorIndex++; m_maxErrorIndex = MAX( m_maxErrorIndex, (m_errorIndex-1) ); return m_errorIndex-1; }
//-----------------------------------------------------------------------------
// exiting block, error isn't in this block, remove.
//-----------------------------------------------------------------------------
void CKeyValues2ErrorStack::Pop() { m_errorIndex--; Assert(m_errorIndex>=0); }
//-----------------------------------------------------------------------------
// Allows you to keep the same stack level, but change the name as you parse peers
//-----------------------------------------------------------------------------
void CKeyValues2ErrorStack::Reset( int stackLevel, CUtlSymbolLarge symName ) { Assert( stackLevel >= 0 && stackLevel < m_errorIndex ); m_errorStack[stackLevel] = symName; }
//-----------------------------------------------------------------------------
// Hit an error, report it and the parsing stack for context
//-----------------------------------------------------------------------------
void CKeyValues2ErrorStack::ReportError( const char *pFmt, ... ) { char temp[2048];
va_list args; va_start( args, pFmt ); Q_vsnprintf( temp, sizeof( temp ), pFmt, args ); va_end( args );
char temp2[2048]; Q_snprintf( temp2, sizeof( temp2 ), "%s(%d) : %s\n", m_pFilename, m_nFileLine, temp ); Warning( temp2 );
for ( int i = 0; i < m_maxErrorIndex; i++ ) { if ( !m_errorStack[i].IsValid() ) continue;
if ( i < m_errorIndex ) { Warning( "%s, ", m_errorStack[i].String() ); } else { Warning( "(*%s*), ", m_errorStack[i].String() ); } } Warning( "\n" ); }
//-----------------------------------------------------------------------------
// a simple helper that creates stack entries as it goes in & out of scope
//-----------------------------------------------------------------------------
class CKeyValues2ErrorContext { public: CKeyValues2ErrorContext( const char *pSymName ) { Init( g_pDataModel->GetSymbol( pSymName ) ); }
CKeyValues2ErrorContext( CUtlSymbolLarge symName ) { Init( symName ); }
~CKeyValues2ErrorContext() { g_KeyValues2ErrorStack.Pop(); }
void Reset( CUtlSymbolLarge symName ) { g_KeyValues2ErrorStack.Reset( m_stackLevel, symName ); }
private: void Init( CUtlSymbolLarge symName ) { m_stackLevel = g_KeyValues2ErrorStack.Push( symName ); }
int m_stackLevel; };
//-----------------------------------------------------------------------------
// Serialization class for Key Values 2
//-----------------------------------------------------------------------------
class CDmSerializerKeyValues2 : public IDmSerializer { public: CDmSerializerKeyValues2( bool bFlatMode ) : m_bFlatMode( bFlatMode ) {}
// Inherited from IDMSerializer
virtual const char *GetName() const { return m_bFlatMode ? "keyvalues2_flat" : "keyvalues2"; } virtual const char *GetDescription() const { return m_bFlatMode ? "KeyValues2 (flat)" : "KeyValues2"; } virtual bool StoresVersionInFile() const { return true; } virtual bool IsBinaryFormat() const { return false; } virtual int GetCurrentVersion() const { return 1; } virtual int GetImportedVersion() const { return 1; } virtual bool Serialize( CUtlBuffer &buf, CDmElement *pRoot ); virtual bool Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, const char *pSourceFormatName, int nSourceFormatVersion, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ); virtual const char *GetImportedFormat() const { return NULL; }
private: enum TokenType_t { TOKEN_INVALID = -1, // A bogus token
TOKEN_OPEN_BRACE, // {
TOKEN_CLOSE_BRACE, // }
TOKEN_OPEN_BRACKET, // [
TOKEN_CLOSE_BRACKET, // ]
TOKEN_COMMA, // ,
// TOKEN_STRING, // Any non-quoted string
TOKEN_DELIMITED_STRING, // Any quoted string
TOKEN_INCLUDE, // #include
TOKEN_EOF, // End of buffer
};
// Methods related to serialization
void SerializeArrayAttribute( CUtlBuffer& buf, CDmAttribute *pAttribute ); void SerializeElementAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ); void SerializeElementArrayAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ); bool SerializeAttributes( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement ); bool SaveElement( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement, bool bWriteDelimiters = true );
// Methods related to unserialization
void EatWhitespacesAndComments( CUtlBuffer &buf ); TokenType_t ReadToken( CUtlBuffer &buf, CUtlBuffer &token ); DmElementDictHandle_t CreateDmElement( const char *pElementType ); bool UnserializeAttributeValueFromToken( CDmAttribute *pAttribute, CUtlBuffer &tokenBuf ); bool UnserializeElementAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, const char *pElementType ); bool UnserializeElementArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName ); bool UnserializeArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ); bool UnserializeAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ); bool UnserializeElement( CUtlBuffer &buf, const char *pElementType, DmElementDictHandle_t *pHandle ); bool UnserializeElement( CUtlBuffer &buf, DmElementDictHandle_t *pHandle ); bool UnserializeElements( CUtlBuffer &buf, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot );
// For unserialization
CDmElementDictionary m_ElementDict; DmElementDictHandle_t m_hRoot; bool m_bFlatMode; DmConflictResolution_t m_idConflictResolution; DmFileId_t m_fileid; };
//-----------------------------------------------------------------------------
// Singleton instance
//-----------------------------------------------------------------------------
static CDmSerializerKeyValues2 s_DMSerializerKeyValues2( false ); static CDmSerializerKeyValues2 s_DMSerializerKeyValues2Flat( true );
void InstallKeyValues2Serializer( IDataModel *pFactory ) { pFactory->AddSerializer( &s_DMSerializerKeyValues2 ); pFactory->AddSerializer( &s_DMSerializerKeyValues2Flat ); }
//-----------------------------------------------------------------------------
// Serializes a single element attribute
//-----------------------------------------------------------------------------
void CDmSerializerKeyValues2::SerializeElementAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ) { CDmElement *pElement = pAttribute->GetValueElement<CDmElement>(); if ( dict.ShouldInlineElement( pElement ) ) { buf.Printf( "\"%s\"\n{\n", pElement->GetTypeString() ); if ( pElement ) { SaveElement( buf, dict, pElement, false ); } buf.Printf( "}\n" ); } else { buf.Printf( "\"%s\" \"", g_pDataModel->GetAttributeNameForType( AT_ELEMENT ) ); if ( pElement ) { ::Serialize( buf, pElement->GetId() ); } buf.PutChar( '\"' ); } }
//-----------------------------------------------------------------------------
// Serializes an array element attribute
//-----------------------------------------------------------------------------
void CDmSerializerKeyValues2::SerializeElementArrayAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ) { CDmrElementArray<> array( pAttribute );
buf.Printf( "\n[\n" ); buf.PushTab();
int nCount = array.Count(); for ( int i = 0; i < nCount; ++i ) { CDmElement *pElement = array[i]; if ( dict.ShouldInlineElement( pElement ) ) { buf.Printf( "\"%s\"\n{\n", pElement->GetTypeString() ); if ( pElement ) { SaveElement( buf, dict, pElement, false ); } buf.PutChar( '}' ); } else { const char *pAttributeType = AttributeTypeName( AT_ELEMENT ); buf.Printf( "\"%s\" \"", pAttributeType ); if ( pElement ) { ::Serialize( buf, pElement->GetId() ); } buf.PutChar( '\"' ); }
if ( i != nCount - 1 ) { buf.PutChar( ',' ); } buf.PutChar( '\n' ); }
buf.PopTab(); buf.Printf( "]" ); }
//-----------------------------------------------------------------------------
// Serializes array attributes
//-----------------------------------------------------------------------------
void CDmSerializerKeyValues2::SerializeArrayAttribute( CUtlBuffer& buf, CDmAttribute *pAttribute ) { CDmrGenericArray array( pAttribute ); int nCount = array.Count();
buf.PutString( "\n[\n" ); buf.PushTab();
for ( int i = 0; i < nCount; ++i ) { if ( pAttribute->GetType() != AT_STRING_ARRAY ) { buf.PutChar( '\"' ); buf.PushTab(); }
array.GetAttribute()->SerializeElement( i, buf );
if ( pAttribute->GetType() != AT_STRING_ARRAY ) { buf.PopTab(); buf.PutChar( '\"' ); }
if ( i != nCount - 1 ) { buf.PutChar( ',' ); } buf.PutChar( '\n' ); } buf.PopTab(); buf.PutChar( ']' ); }
//-----------------------------------------------------------------------------
// Serializes all attributes in an element
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::SerializeAttributes( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement ) { // Collect the attributes to be written
CDmAttribute **ppAttributes = ( CDmAttribute** )_alloca( pElement->AttributeCount() * sizeof( CDmAttribute* ) ); int nAttributes = 0; for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) { if ( pAttribute->IsFlagSet( FATTRIB_DONTSAVE ) ) continue;
ppAttributes[ nAttributes++ ] = pAttribute; }
// Now write them all out in reverse order, since FirstAttribute is actually the *last* attribute for perf reasons
for ( int i = nAttributes - 1; i >= 0; --i ) { CDmAttribute *pAttribute = ppAttributes[ i ]; Assert( pAttribute );
const char *pName = pAttribute->GetName( ); DmAttributeType_t nAttrType = pAttribute->GetType(); if ( nAttrType != AT_ELEMENT ) { buf.Printf( "\"%s\" \"%s\" ", pName, g_pDataModel->GetAttributeNameForType( nAttrType ) ); } else { // Elements either serialize their type name or "element" depending on whether they are inlined
buf.Printf( "\"%s\" ", pName ); }
switch( nAttrType ) { default: if ( nAttrType >= AT_FIRST_ARRAY_TYPE ) { SerializeArrayAttribute( buf, pAttribute ); } else { if ( pAttribute->SerializesOnMultipleLines() ) { buf.PutChar( '\n' ); }
buf.PutChar( '\"' ); buf.PushTab(); pAttribute->Serialize( buf ); buf.PopTab(); buf.PutChar( '\"' ); } break;
case AT_STRING: // Don't explicitly add string delimiters; serialization does that.
pAttribute->Serialize( buf ); break;
case AT_ELEMENT: SerializeElementAttribute( buf, dict, pAttribute ); break;
case AT_ELEMENT_ARRAY: SerializeElementArrayAttribute( buf, dict, pAttribute ); break; }
buf.PutChar( '\n' ); }
return true; }
bool CDmSerializerKeyValues2::SaveElement( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement, bool bWriteDelimiters ) { if ( bWriteDelimiters ) { buf.Printf( "\"%s\"\n{\n", pElement->GetTypeString() ); } buf.PushTab();
// explicitly serialize id, now that it's no longer an attribute
buf.PutString( "\"id\" \"elementid\" " ); buf.PutChar( '\"' ); ::Serialize( buf, pElement->GetId() ); buf.PutString( "\"\n" );
SerializeAttributes( buf, dict, pElement );
buf.PopTab(); if ( bWriteDelimiters ) { buf.Printf( "}\n" ); } return true; }
bool CDmSerializerKeyValues2::Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ) { SetSerializationDelimiter( GetCStringCharConversion() ); SetSerializationArrayDelimiter( "," );
// Save elements, attribute links
CDmElementSerializationDictionary dict; dict.BuildElementList( pRoot, m_bFlatMode );
// Save elements to buffer
DmElementDictHandle_t i; for ( i = dict.FirstRootElement(); i != ELEMENT_DICT_HANDLE_INVALID; i = dict.NextRootElement(i) ) { SaveElement( outBuf, dict, dict.GetRootElement( i ) ); outBuf.PutChar( '\n' ); }
SetSerializationDelimiter( NULL ); SetSerializationArrayDelimiter( NULL );
return true; }
//-----------------------------------------------------------------------------
// Eats whitespaces and c++ style comments
//-----------------------------------------------------------------------------
#pragma warning (disable:4706)
void CDmSerializerKeyValues2::EatWhitespacesAndComments( CUtlBuffer &buf ) { // eating white spaces and remarks loop
int nMaxPut = buf.TellMaxPut() - buf.TellGet(); int nOffset = 0; while ( nOffset < nMaxPut ) { // Eat whitespaces, keep track of line count
const char *pPeek = NULL; while ( pPeek = (const char *)buf.PeekGet( sizeof(char), nOffset ) ) { if ( !V_isspace( *pPeek ) ) break;
if ( *pPeek == '\n' ) { g_KeyValues2ErrorStack.IncrementCurrentLine(); } if ( ++nOffset >= nMaxPut ) break; }
// If we don't have a a c++ style comment next, we're done
pPeek = (const char *)buf.PeekGet( 2 * sizeof(char), nOffset ); if ( ( nOffset >= nMaxPut ) || !pPeek || ( pPeek[0] != '/' ) || ( pPeek[1] != '/' ) ) break;
// Deal with c++ style comments
nOffset += 2;
// read complete line
while ( pPeek = (const char *)buf.PeekGet( sizeof(char), nOffset ) ) { if ( *pPeek == '\n' ) break; if ( ++nOffset >= nMaxPut ) break; }
g_KeyValues2ErrorStack.IncrementCurrentLine(); }
buf.SeekGet( CUtlBuffer::SEEK_CURRENT, nOffset ); }
#pragma warning (default:4706)
//-----------------------------------------------------------------------------
// Reads a single token, points the token utlbuffer at it
//-----------------------------------------------------------------------------
CDmSerializerKeyValues2::TokenType_t CDmSerializerKeyValues2::ReadToken( CUtlBuffer &buf, CUtlBuffer &token ) { EatWhitespacesAndComments( buf );
// if message text buffers go over this size
// change this value to make sure they will fit
// affects loading of last active chat window
if ( !buf.IsValid() || ( buf.TellGet() == buf.TellMaxPut() ) ) return TOKEN_EOF;
// Compute token length and type
int nLength = 0; TokenType_t t = TOKEN_INVALID; char c = *((const char *)buf.PeekGet()); switch( c ) { case '{': nLength = 1; t = TOKEN_OPEN_BRACE; break;
case '}': nLength = 1; t = TOKEN_CLOSE_BRACE; break;
case '[': nLength = 1; t = TOKEN_OPEN_BRACKET; break;
case ']': nLength = 1; t = TOKEN_CLOSE_BRACKET; break;
case ',': nLength = 1; t = TOKEN_COMMA; break;
case '\"': // NOTE: The -1 is because peek includes room for the /0
nLength = buf.PeekDelimitedStringLength( GetCStringCharConversion(), false ) - 1; if ( (nLength <= 1) || ( *(const char *)buf.PeekGet( nLength - 1 ) != '\"' )) { g_KeyValues2ErrorStack.ReportError( "Unexpected EOF in quoted string" ); t = TOKEN_INVALID; } else { t = TOKEN_DELIMITED_STRING; } break;
default: t = TOKEN_INVALID; break; }
token.EnsureCapacity( nLength ); buf.Get( token.Base(), nLength ); token.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); token.SeekPut( CUtlBuffer::SEEK_HEAD, nLength );
// Count the number of crs in the token + update the current line
const char *pMem = (const char *)token.Base(); for ( int i = 0; i < nLength; ++i ) { if ( pMem[i] == '\n' ) { g_KeyValues2ErrorStack.IncrementCurrentLine(); } }
return t; }
//-----------------------------------------------------------------------------
// Creates a scene object, adds it to the element dictionary
//-----------------------------------------------------------------------------
DmElementDictHandle_t CDmSerializerKeyValues2::CreateDmElement( const char *pElementType ) { // See if we can create an element of that type
DmElementHandle_t hElement = g_pDataModel->CreateElement( pElementType, "", m_fileid ); if ( hElement == DMELEMENT_HANDLE_INVALID ) { g_KeyValues2ErrorStack.ReportError("Element uses unknown element type %s\n", pElementType ); return ELEMENT_DICT_HANDLE_INVALID; }
CDmElement *pElement = g_pDataModel->GetElement( hElement ); CDmeElementAccessor::DisableOnChangedCallbacks( pElement ); return m_ElementDict.InsertElement( pElement ); }
//-----------------------------------------------------------------------------
// Reads an attribute for an element
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::UnserializeElementAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, const char *pElementType ) { CDmElement *pElement = m_ElementDict.GetElement( hElement ); CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, AT_ELEMENT ); if ( !pAttribute ) { g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of unknown type %s!\n", pAttributeName, pElementType ); return false; }
DmElementDictHandle_t h; bool bOk = UnserializeElement( buf, pElementType, &h ); if ( bOk ) { CDmElement *pNewElement = m_ElementDict.GetElement( h ); pAttribute->SetValue( pNewElement ? pNewElement->GetHandle() : DMELEMENT_HANDLE_INVALID ); } return bOk; }
//-----------------------------------------------------------------------------
// Reads an attribute for an element array
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::UnserializeElementArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName ) { CDmElement *pElement = m_ElementDict.GetElement( hElement ); CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, AT_ELEMENT_ARRAY ); if ( !pAttribute ) { g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of an inappropriate type!\n", pAttributeName ); return false; }
// Arrays first must have a '[' specified
TokenType_t token; CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); CUtlCharConversion *pConv = GetCStringCharConversion();
token = ReadToken( buf, tokenBuf ); if ( token != TOKEN_OPEN_BRACKET ) { g_KeyValues2ErrorStack.ReportError( "Expecting '[', didn't find it!" ); return false; }
int nElementIndex = 0;
// Now read a list of array values, separated by commas
while ( buf.IsValid() ) { token = ReadToken( buf, tokenBuf ); if ( token == TOKEN_INVALID || token == TOKEN_EOF ) { g_KeyValues2ErrorStack.ReportError( "Expecting ']', didn't find it!" ); return false; }
// Then, keep reading until we hit a ']'
if ( token == TOKEN_CLOSE_BRACKET ) break;
// If we've already read in an array value, we need to read a comma next
if ( nElementIndex > 0 ) { if ( token != TOKEN_COMMA ) { g_KeyValues2ErrorStack.ReportError( "Expecting ',', didn't find it!" ); return false; }
// Read in the next thing, which should be a value
token = ReadToken( buf, tokenBuf ); }
// Ok, we must be reading an array type value
if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting element type, didn't find it!" ); return false; }
// Get the element type out
char elementType[ 256 ]; tokenBuf.GetDelimitedString( pConv, elementType, sizeof( elementType ) );
// Use the element type to figure out if we're using a element reference or an inlined element
if ( !V_strcmp( elementType, g_pDataModel->GetAttributeNameForType( AT_ELEMENT ) ) ) { token = ReadToken( buf, tokenBuf );
// Ok, we must be reading an array type value
if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting element reference, didn't find it!" ); return false; }
// Get the element type out
char elementId[ 256 ]; tokenBuf.GetDelimitedString( pConv, elementId, sizeof( elementId ) );
if ( elementId[ 0 ] == '\0' ) { m_ElementDict.AddArrayAttribute( pAttribute, ELEMENT_DICT_HANDLE_INVALID ); } else { DmObjectId_t id; if ( !UniqueIdFromString( &id, elementId ) ) { g_KeyValues2ErrorStack.ReportError( "Encountered invalid element ID data!" ); return false; }
Assert( IsUniqueIdValid( id ) ); m_ElementDict.AddArrayAttribute( pAttribute, id ); } } else { DmElementDictHandle_t hArrayElement; bool bOk = UnserializeElement( buf, elementType, &hArrayElement ); if ( !bOk ) return false; m_ElementDict.AddArrayAttribute( pAttribute, hArrayElement ); }
// Ok, we've read in another value
++nElementIndex; }
return true; }
//-----------------------------------------------------------------------------
// Unserializes an attribute from a token buffer
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::UnserializeAttributeValueFromToken( CDmAttribute *pAttribute, CUtlBuffer &tokenBuf ) { // NOTE: This code is necessary because the attribute code is using Scanf
// which is not really friendly toward delimiters, so we must pass in
// non-delimited buffers. Sucky. There must be a better way of doing this
const char *pBuf = (const char*)tokenBuf.Base(); int nLength = tokenBuf.TellMaxPut(); char *pTemp = (char*)stackalloc( nLength + 1 );
bool bIsString = ( pAttribute->GetType() == AT_STRING ) || ( pAttribute->GetType() == AT_STRING_ARRAY ); if ( !bIsString ) { nLength = tokenBuf.PeekDelimitedStringLength( GetCStringCharConversion() ); tokenBuf.GetDelimitedString( GetCStringCharConversion(), pTemp, nLength + 1 ); pBuf = pTemp; } else { SetSerializationDelimiter( GetCStringCharConversion() ); }
bool bOk; CUtlBuffer buf( pBuf, nLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); if ( pAttribute->GetType() < AT_FIRST_ARRAY_TYPE ) { bOk = pAttribute->Unserialize( buf ); } else { bOk = pAttribute->UnserializeElement( buf ); }
if ( bIsString ) { SetSerializationDelimiter( NULL ); }
return bOk; }
//-----------------------------------------------------------------------------
// Reads an attribute for an element array
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::UnserializeArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ) { CDmElement *pElement = m_ElementDict.GetElement( hElement ); CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, nAttrType ); if ( !pAttribute ) { g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of an inappropriate type %s!\n", pAttributeName, g_pDataModel->GetAttributeNameForType( nAttrType ) ); return false; }
// Arrays first must have a '[' specified
TokenType_t token; CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); token = ReadToken( buf, tokenBuf ); if ( token != TOKEN_OPEN_BRACKET ) { g_KeyValues2ErrorStack.ReportError( "Expecting '[', didn't find it!" ); return false; }
int nElementIndex = 0;
// Now read a list of array values, separated by commas
while ( buf.IsValid() ) { token = ReadToken( buf, tokenBuf ); if ( token == TOKEN_INVALID || token == TOKEN_EOF ) { g_KeyValues2ErrorStack.ReportError( "Expecting ']', didn't find it!" ); return false; }
// Then, keep reading until we hit a ']'
if ( token == TOKEN_CLOSE_BRACKET ) break;
// If we've already read in an array value, we need to read a comma next
if ( nElementIndex > 0 ) { if ( token != TOKEN_COMMA ) { g_KeyValues2ErrorStack.ReportError( "Expecting ',', didn't find it!" ); return false; }
// Read in the next thing, which should be a value
token = ReadToken( buf, tokenBuf ); }
// Ok, we must be reading an attributearray value
if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting array attribute value, didn't find it!" ); return false; }
if ( !UnserializeAttributeValueFromToken( pAttribute, tokenBuf ) ) { g_KeyValues2ErrorStack.ReportError("Error reading in array attribute \"%s\" element %d", pAttributeName, nElementIndex ); return false; }
// Ok, we've read in another value
++nElementIndex; }
return true; }
//-----------------------------------------------------------------------------
// Reads an attribute for an element
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::UnserializeAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ) { // Read the attribute value
CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); TokenType_t token = ReadToken( buf, tokenBuf ); if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting quoted attribute value for attribute \"%s\", didn't find one!", pAttributeName ); return false; }
CDmElement *pElement = m_ElementDict.GetElement( hElement ); CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, nAttrType ); if ( !pAttribute ) { g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of an inappropriate type %s!\n", pAttributeName, g_pDataModel->GetAttributeNameForType( nAttrType ) ); return false; } switch( nAttrType ) { case AT_ELEMENT: { // Get the attribute value out
CUtlCharConversion *pConv = GetCStringCharConversion(); int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); char *pAttributeValue = (char*)stackalloc( nLength * sizeof(char) ); tokenBuf.GetDelimitedString( pConv, pAttributeValue, nLength );
// No string? that's ok, it means we have a NULL pointer
if ( !pAttributeValue[0] ) return true;
DmObjectId_t id; if ( !UniqueIdFromString( &id, pAttributeValue ) ) { g_KeyValues2ErrorStack.ReportError("Invalid format for element ID encountered for attribute \"%s\"", pAttributeName ); return false; }
m_ElementDict.AddAttribute( pAttribute, id ); } return true;
default: if ( UnserializeAttributeValueFromToken( pAttribute, tokenBuf ) ) return true;
g_KeyValues2ErrorStack.ReportError("Error reading attribute \"%s\"", pAttributeName ); return false; } }
/*
//-----------------------------------------------------------------------------
// Purpose:
// Input : includedKeys -
//-----------------------------------------------------------------------------
void KeyValues::AppendIncludedKeys( CUtlVector< KeyValues * >& includedKeys ) { // Append any included keys, too...
int includeCount = includedKeys.Count(); int i; for ( i = 0; i < includeCount; i++ ) { KeyValues *kv = includedKeys[ i ]; Assert( kv );
KeyValues *insertSpot = this; while ( insertSpot->GetNextKey() ) { insertSpot = insertSpot->GetNextKey(); }
insertSpot->SetNextKey( kv ); } }
void KeyValues::ParseIncludedKeys( char const *resourceName, const char *filetoinclude, IBaseFileSystem* pFileSystem, const char *pPathID, CUtlVector< KeyValues * >& includedKeys ) { Assert( resourceName ); Assert( filetoinclude ); Assert( pFileSystem ); // Load it...
if ( !pFileSystem ) { return; }
// Get relative subdirectory
char fullpath[ 512 ]; Q_strncpy( fullpath, resourceName, sizeof( fullpath ) );
// Strip off characters back to start or first /
bool done = false; int len = Q_strlen( fullpath ); while ( !done ) { if ( len <= 0 ) { break; } if ( fullpath[ len - 1 ] == '\\' || fullpath[ len - 1 ] == '/' ) { break; }
// zero it
fullpath[ len - 1 ] = 0; --len; }
// Append included file
Q_strncat( fullpath, filetoinclude, sizeof( fullpath ), COPY_ALL_CHARACTERS );
KeyValues *newKV = new KeyValues( fullpath );
// CUtlSymbolLarge save = s_CurrentFileSymbol; // did that had any use ???
newKV->UsesEscapeSequences( m_bHasEscapeSequences ); // use same format as parent
if ( newKV->LoadFromFile( pFileSystem, fullpath, pPathID ) ) { includedKeys.AddToTail( newKV ); } else { DevMsg( "KeyValues::ParseIncludedKeys: Couldn't load included keyvalue file %s\n", fullpath ); newKV->deleteThis(); }
// s_CurrentFileSymbol = save;
}
//-----------------------------------------------------------------------------
// Read from a buffer...
//-----------------------------------------------------------------------------
bool KeyValues::LoadFromBuffer( char const *resourceName, const char *pBuffer, IBaseFileSystem* pFileSystem , const char *pPathID ) { char *pfile = const_cast<char *>(pBuffer);
KeyValues *pPreviousKey = NULL; KeyValues *pCurrentKey = this; CUtlVector< KeyValues * > includedKeys; bool wasQuoted; g_KeyValues2ErrorStack.SetFilename( resourceName ); do { // the first thing must be a key
const char *s = ReadToken( &pfile, wasQuoted ); if ( !pfile || !s || *s == 0 ) break;
if ( !Q_stricmp( s, "#include" ) ) // special include macro (not a key name)
{ s = ReadToken( &pfile, wasQuoted ); // Name of subfile to load is now in s
if ( !s || *s == 0 ) { g_KeyValues2ErrorStack.ReportError("#include is NULL " ); } else { ParseIncludedKeys( resourceName, s, pFileSystem, pPathID, includedKeys ); }
continue; }
if ( !pCurrentKey ) { pCurrentKey = new KeyValues( s ); Assert( pCurrentKey );
pCurrentKey->UsesEscapeSequences( m_bHasEscapeSequences ); // same format has parent use
if ( pPreviousKey ) { pPreviousKey->SetNextKey( pCurrentKey ); } } else { pCurrentKey->SetName( s ); }
// get the '{'
s = ReadToken( &pfile, wasQuoted );
if ( s && *s == '{' && !wasQuoted ) { // header is valid so load the file
pCurrentKey->RecursiveLoadFromBuffer( resourceName, &pfile ); } else { g_KeyValues2ErrorStack.ReportError("LoadFromBuffer: missing {" ); }
pPreviousKey = pCurrentKey; pCurrentKey = NULL; } while ( pfile != NULL );
AppendIncludedKeys( includedKeys );
g_KeyValues2ErrorStack.SetFilename( "" );
return true; } */
//-----------------------------------------------------------------------------
// Unserializes a single element given the type name
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::UnserializeElement( CUtlBuffer &buf, const char *pElementType, DmElementDictHandle_t *pHandle ) { *pHandle = ELEMENT_DICT_HANDLE_INVALID;
// Create the element
DmElementDictHandle_t hElement = CreateDmElement( pElementType ); if ( hElement == ELEMENT_DICT_HANDLE_INVALID ) return false;
// Report errors relative to this type name
CKeyValues2ErrorContext errorReport( pElementType );
TokenType_t token; CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); CUtlCharConversion *pConv = GetCStringCharConversion();
// Then we expect a '{'
token = ReadToken( buf, tokenBuf ); if ( token != TOKEN_OPEN_BRACE ) { g_KeyValues2ErrorStack.ReportError( "Expecting '{', didn't find it!" ); return false; }
while ( buf.IsValid() ) { token = ReadToken( buf, tokenBuf ); if ( token == TOKEN_INVALID || token == TOKEN_EOF ) { g_KeyValues2ErrorStack.ReportError( "Expecting '}', didn't find it!" ); return false; }
// Then, keep reading until we hit a '}'
if ( token == TOKEN_CLOSE_BRACE ) break;
// Ok, we must be reading an attribute
if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting attribute name, didn't find it!" ); return false; }
// First, read an attribute name
char attributeName[ 256 ]; tokenBuf.GetDelimitedString( pConv, attributeName, sizeof( attributeName ) );
// Next, read an attribute type
token = ReadToken( buf, tokenBuf ); if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting attribute type for attribute %s, didn't find it!", attributeName ); return false; }
char attributeType[ 256 ]; tokenBuf.GetDelimitedString( pConv, attributeType, sizeof( attributeType ) );
// read element's id (or any other id attribute, if any)
if ( !V_strcmp( attributeType, "elementid" ) ) { // Next, read the id
token = ReadToken( buf, tokenBuf ); if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting attribute type for attribute %s, didn't find it!", attributeName ); return false; }
char elementId[ 256 ]; tokenBuf.GetDelimitedString( pConv, elementId, sizeof( elementId ) );
Assert( !V_stricmp( attributeName, "id" ) ); if ( !V_stricmp( attributeName, "id" ) ) { DmObjectId_t id; if ( !UniqueIdFromString( &id, elementId ) ) { g_KeyValues2ErrorStack.ReportError( "Encountered invalid element ID data!" ); return false; }
m_ElementDict.SetElementId( hElement, id, m_idConflictResolution ); } continue; } DmAttributeType_t nAttrType = g_pDataModel->GetAttributeTypeForName( attributeType );
// Next, read an attribute value
bool bOk = true; switch( nAttrType ) { case AT_UNKNOWN: bOk = UnserializeElementAttribute( buf, hElement, attributeName, attributeType ); break;
case AT_ELEMENT_ARRAY: bOk = UnserializeElementArrayAttribute( buf, hElement, attributeName ); break;
default: if ( nAttrType >= AT_FIRST_ARRAY_TYPE ) { bOk = UnserializeArrayAttribute( buf, hElement, attributeName, nAttrType ); } else { bOk = UnserializeAttribute( buf, hElement, attributeName, nAttrType ); } break; }
if ( !bOk ) return false; }
*pHandle = hElement; return true; }
//-----------------------------------------------------------------------------
// Unserializes a single element
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::UnserializeElement( CUtlBuffer &buf, DmElementDictHandle_t *pHandle ) { *pHandle = ELEMENT_DICT_HANDLE_INVALID;
// First, read the type name
CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER );
TokenType_t token = ReadToken( buf, tokenBuf ); if ( token == TOKEN_INVALID ) return false; if ( token == TOKEN_EOF ) return true;
// Get the type name out
if ( token != TOKEN_DELIMITED_STRING ) { g_KeyValues2ErrorStack.ReportError( "Expecting element type name, didn't find it!" ); return false; }
CUtlCharConversion* pConv = GetCStringCharConversion(); int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); char *pTypeName = (char*)stackalloc( nLength * sizeof(char) ); tokenBuf.GetDelimitedString( pConv, pTypeName, nLength );
return UnserializeElement( buf, pTypeName, pHandle ); }
//-----------------------------------------------------------------------------
// Main entry point for the unserialization
//-----------------------------------------------------------------------------
bool CDmSerializerKeyValues2::Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, const char *pSourceFormatName, int nSourceFormatVersion, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) { bool bSuccess = UnserializeElements( buf, fileid, idConflictResolution, ppRoot ); if ( !bSuccess ) return false;
return g_pDataModel->UpdateUnserializedElements( pSourceFormatName, nSourceFormatVersion, fileid, idConflictResolution, ppRoot ); }
bool CDmSerializerKeyValues2::UnserializeElements( CUtlBuffer &buf, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) { *ppRoot = NULL;
m_idConflictResolution = idConflictResolution;
m_fileid = fileid;
g_KeyValues2ErrorStack.SetFilename( g_pDataModel->GetFileName( fileid ) ); m_hRoot = ELEMENT_DICT_HANDLE_INVALID; m_ElementDict.Clear();
bool bOk = true; while ( buf.IsValid() ) { DmElementDictHandle_t h; bOk = UnserializeElement( buf, &h ); if ( !bOk || ( h == ELEMENT_DICT_HANDLE_INVALID ) ) break;
if ( m_hRoot == ELEMENT_DICT_HANDLE_INVALID ) { m_hRoot = h; } }
// do this *before* getting the root, since the first element might be deleted due to id conflicts
m_ElementDict.HookUpElementReferences();
*ppRoot = m_ElementDict.GetElement( m_hRoot );
// mark all unserialized elements as done unserializing, and call Resolve()
for ( DmElementDictHandle_t h = m_ElementDict.FirstElement(); h != ELEMENT_DICT_HANDLE_INVALID; h = m_ElementDict.NextElement( h ) ) { CDmElement *pElement = m_ElementDict.GetElement( h ); if ( !pElement ) continue;
CDmeElementAccessor::EnableOnChangedCallbacks( pElement ); CDmeElementAccessor::FinishUnserialization( pElement ); }
m_fileid = DMFILEID_INVALID;
g_pDmElementFrameworkImp->RemoveCleanElementsFromDirtyList( ); m_ElementDict.Clear(); return bOk; }
|