You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1433 lines
42 KiB
1433 lines
42 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "dmxloader/dmxelement.h" |
|
#include <ctype.h> |
|
#include "tier1/utlbuffer.h" |
|
#include "tier1/utlbufferutil.h" |
|
#include <limits.h> |
|
#include "dmxserializationdictionary.h" |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Forward declarations |
|
//----------------------------------------------------------------------------- |
|
class CUtlBuffer; |
|
extern const char *g_pAttributeTypeName[AT_TYPE_COUNT]; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// a simple class to keep track of a stack of valid parsed symbols |
|
//----------------------------------------------------------------------------- |
|
class CDmxKeyValues2ErrorStack |
|
{ |
|
public: |
|
CDmxKeyValues2ErrorStack(); |
|
|
|
// 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( CUtlSymbol 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, CUtlSymbol symName ); |
|
|
|
// Hit an error, report it and the parsing stack for context |
|
void ReportError( const char *pError, ... ); |
|
|
|
static CUtlSymbolTable& GetSymbolTable() { return m_ErrorSymbolTable; } |
|
|
|
private: |
|
enum |
|
{ |
|
MAX_ERROR_STACK = 64 |
|
}; |
|
|
|
CUtlSymbol m_errorStack[MAX_ERROR_STACK]; |
|
const char *m_pFilename; |
|
int m_nFileLine; |
|
int m_errorIndex; |
|
int m_maxErrorIndex; |
|
|
|
static CUtlSymbolTable m_ErrorSymbolTable; |
|
}; |
|
|
|
|
|
CUtlSymbolTable CDmxKeyValues2ErrorStack::m_ErrorSymbolTable; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Singleton instance |
|
//----------------------------------------------------------------------------- |
|
static CDmxKeyValues2ErrorStack g_KeyValues2ErrorStack; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CDmxKeyValues2ErrorStack::CDmxKeyValues2ErrorStack() : |
|
m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0), m_nFileLine(1) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the filename |
|
//----------------------------------------------------------------------------- |
|
void CDmxKeyValues2ErrorStack::SetFilename( const char *pFilename ) |
|
{ |
|
m_pFilename = pFilename; |
|
m_maxErrorIndex = 0; |
|
m_nFileLine = 1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Current line control |
|
//----------------------------------------------------------------------------- |
|
void CDmxKeyValues2ErrorStack::IncrementCurrentLine() |
|
{ |
|
++m_nFileLine; |
|
} |
|
|
|
void CDmxKeyValues2ErrorStack::SetCurrentLine( int nLine ) |
|
{ |
|
m_nFileLine = nLine; |
|
} |
|
|
|
int CDmxKeyValues2ErrorStack::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 CDmxKeyValues2ErrorStack::Push( CUtlSymbol 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 CDmxKeyValues2ErrorStack::Pop() |
|
{ |
|
m_errorIndex--; |
|
Assert(m_errorIndex>=0); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Allows you to keep the same stack level, but change the name as you parse peers |
|
//----------------------------------------------------------------------------- |
|
void CDmxKeyValues2ErrorStack::Reset( int stackLevel, CUtlSymbol symName ) |
|
{ |
|
Assert( stackLevel >= 0 && stackLevel < m_errorIndex ); |
|
m_errorStack[stackLevel] = symName; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Hit an error, report it and the parsing stack for context |
|
//----------------------------------------------------------------------------- |
|
void CDmxKeyValues2ErrorStack::ReportError( const char *pFmt, ... ) |
|
{ |
|
char temp[2048]; |
|
|
|
va_list args; |
|
va_start( args, pFmt ); |
|
Q_vsnprintf( temp, sizeof( temp ), pFmt, args ); |
|
va_end( args ); |
|
|
|
Warning( "%s(%d) : %s\n", m_pFilename, m_nFileLine, temp ); |
|
|
|
for ( int i = 0; i < m_maxErrorIndex; i++ ) |
|
{ |
|
if ( !m_errorStack[i].IsValid() ) |
|
continue; |
|
|
|
if ( i < m_errorIndex ) |
|
{ |
|
Warning( "%s, ", GetSymbolTable().String( m_errorStack[i] ) ); |
|
} |
|
else |
|
{ |
|
Warning( "(*%s*), ", GetSymbolTable().String( m_errorStack[i] ) ); |
|
} |
|
} |
|
Warning( "\n" ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// a simple helper that creates stack entries as it goes in & out of scope |
|
//----------------------------------------------------------------------------- |
|
class CKeyValues2ErrorContext |
|
{ |
|
public: |
|
CKeyValues2ErrorContext( const char *pSymName ) |
|
{ |
|
Init( CDmxKeyValues2ErrorStack::GetSymbolTable().AddString( pSymName ) ); |
|
} |
|
|
|
CKeyValues2ErrorContext( CUtlSymbol symName ) |
|
{ |
|
Init( symName ); |
|
} |
|
|
|
~CKeyValues2ErrorContext() |
|
{ |
|
g_KeyValues2ErrorStack.Pop(); |
|
} |
|
|
|
void Reset( CUtlSymbol symName ) |
|
{ |
|
g_KeyValues2ErrorStack.Reset( m_stackLevel, symName ); |
|
} |
|
|
|
private: |
|
void Init( CUtlSymbol symName ) |
|
{ |
|
m_stackLevel = g_KeyValues2ErrorStack.Push( symName ); |
|
} |
|
|
|
int m_stackLevel; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Element dictionary used in unserialization |
|
//----------------------------------------------------------------------------- |
|
typedef int DmxElementDictHandle_t; |
|
enum |
|
{ |
|
ELEMENT_DICT_HANDLE_INVALID = (DmxElementDictHandle_t)~0 |
|
}; |
|
|
|
|
|
class CDmxElementDictionary |
|
{ |
|
public: |
|
CDmxElementDictionary() = default; |
|
|
|
DmxElementDictHandle_t InsertElement( CDmxElement *pElement ); |
|
CDmxElement *GetElement( DmxElementDictHandle_t handle ); |
|
void AddAttribute( CDmxAttribute *pAttribute, const DmObjectId_t &pElementId ); |
|
void AddArrayAttribute( CDmxAttribute *pAttribute, DmxElementDictHandle_t hChild ); |
|
void AddArrayAttribute( CDmxAttribute *pAttribute, const DmObjectId_t &pElementId ); |
|
|
|
// Finds an element into the table |
|
DmxElementDictHandle_t FindElement( CDmxElement *pElement ); |
|
DmxElementDictHandle_t FindElement( const DmObjectId_t &objectId ); |
|
|
|
// Sets the element id for an element |
|
void SetElementId( DmxElementDictHandle_t hElement, const DmObjectId_t &objectId ); |
|
|
|
// Hook up all element references (which were unserialized as object ids) |
|
void HookUpElementReferences(); |
|
|
|
// Clears the dictionary |
|
void Clear(); |
|
|
|
// iteration through elements |
|
DmxElementDictHandle_t FirstElement() { return 0; } |
|
DmxElementDictHandle_t NextElement( DmxElementDictHandle_t h ) |
|
{ |
|
return m_Dict.IsValidIndex( h+1 ) ? h+1 : ELEMENT_DICT_HANDLE_INVALID; |
|
} |
|
|
|
private: |
|
struct DictInfo_t |
|
{ |
|
CDmxElement *m_pElement; |
|
DmObjectId_t m_Id; |
|
}; |
|
|
|
struct AttributeInfo_t |
|
{ |
|
CDmxAttribute *m_pAttribute; |
|
DmAttributeType_t m_nType; // AT_ELEMENT or AT_OBJECTID |
|
union |
|
{ |
|
DmxElementDictHandle_t m_hElement; |
|
DmObjectId_t m_ObjectId; |
|
}; |
|
}; |
|
typedef CUtlVector<AttributeInfo_t> AttributeList_t; |
|
|
|
// Hook up all element references (which were unserialized as object ids) |
|
void HookUpElementAttributes(); |
|
void HookUpElementArrayAttributes(); |
|
|
|
CUtlVector< DictInfo_t > m_Dict; |
|
AttributeList_t m_Attributes; |
|
AttributeList_t m_ArrayAttributes; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Clears the dictionary |
|
//----------------------------------------------------------------------------- |
|
void CDmxElementDictionary::Clear() |
|
{ |
|
m_Dict.Purge(); |
|
m_Attributes.Purge(); |
|
m_ArrayAttributes.Purge(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Inserts an element into the table |
|
//----------------------------------------------------------------------------- |
|
DmxElementDictHandle_t CDmxElementDictionary::InsertElement( CDmxElement *pElement ) |
|
{ |
|
// Insert it into the reconnection table |
|
DmxElementDictHandle_t h = m_Dict.AddToTail( ); |
|
m_Dict[h].m_pElement = pElement; |
|
InvalidateUniqueId( &m_Dict[h].m_Id ); |
|
return h; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the element id for an element |
|
//----------------------------------------------------------------------------- |
|
void CDmxElementDictionary::SetElementId( DmxElementDictHandle_t hElement, const DmObjectId_t &objectId ) |
|
{ |
|
Assert( hElement != ELEMENT_DICT_HANDLE_INVALID ); |
|
CopyUniqueId( objectId, &m_Dict[hElement].m_Id ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a particular element |
|
//----------------------------------------------------------------------------- |
|
CDmxElement *CDmxElementDictionary::GetElement( DmxElementDictHandle_t handle ) |
|
{ |
|
if ( handle == ELEMENT_DICT_HANDLE_INVALID ) |
|
return NULL; |
|
|
|
return m_Dict[ handle ].m_pElement; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds an attribute to the fixup list |
|
//----------------------------------------------------------------------------- |
|
void CDmxElementDictionary::AddAttribute( CDmxAttribute *pAttribute, const DmObjectId_t &objectId ) |
|
{ |
|
int i = m_Attributes.AddToTail(); |
|
m_Attributes[i].m_nType = AT_OBJECTID; |
|
m_Attributes[i].m_pAttribute = pAttribute; |
|
CopyUniqueId( objectId, &m_Attributes[i].m_ObjectId ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds an element of an attribute array to the fixup list |
|
//----------------------------------------------------------------------------- |
|
void CDmxElementDictionary::AddArrayAttribute( CDmxAttribute *pAttribute, DmxElementDictHandle_t hElement ) |
|
{ |
|
int i = m_ArrayAttributes.AddToTail(); |
|
m_ArrayAttributes[i].m_nType = AT_ELEMENT; |
|
m_ArrayAttributes[i].m_pAttribute = pAttribute; |
|
m_ArrayAttributes[i].m_hElement = hElement; |
|
} |
|
|
|
void CDmxElementDictionary::AddArrayAttribute( CDmxAttribute *pAttribute, const DmObjectId_t &objectId ) |
|
{ |
|
int i = m_ArrayAttributes.AddToTail(); |
|
m_ArrayAttributes[i].m_nType = AT_OBJECTID; |
|
m_ArrayAttributes[i].m_pAttribute = pAttribute; |
|
CopyUniqueId( objectId, &m_ArrayAttributes[i].m_ObjectId ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds an element into the table |
|
//----------------------------------------------------------------------------- |
|
DmxElementDictHandle_t CDmxElementDictionary::FindElement( CDmxElement *pElement ) |
|
{ |
|
int nCount = m_Dict.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( pElement == m_Dict[i].m_pElement ) |
|
return i; |
|
} |
|
return ELEMENT_DICT_HANDLE_INVALID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds an element into the table |
|
//----------------------------------------------------------------------------- |
|
DmxElementDictHandle_t CDmxElementDictionary::FindElement( const DmObjectId_t &objectId ) |
|
{ |
|
int nCount = m_Dict.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( IsUniqueIdEqual( objectId, m_Dict[i].m_Id ) ) |
|
return i; |
|
} |
|
return ELEMENT_DICT_HANDLE_INVALID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Hook up all element references (which were unserialized as object ids) |
|
//----------------------------------------------------------------------------- |
|
void CDmxElementDictionary::HookUpElementAttributes() |
|
{ |
|
int n = m_Attributes.Count(); |
|
for ( int i = 0; i < n; ++i ) |
|
{ |
|
Assert( m_Attributes[i].m_nType == AT_OBJECTID ); |
|
|
|
DmxElementDictHandle_t hElement = FindElement( m_Attributes[i].m_ObjectId ); |
|
CDmxElement *pElement = GetElement( hElement ); |
|
m_Attributes[i].m_pAttribute->SetValue( pElement ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Hook up all element array references |
|
//----------------------------------------------------------------------------- |
|
void CDmxElementDictionary::HookUpElementArrayAttributes() |
|
{ |
|
int n = m_ArrayAttributes.Count(); |
|
for ( int i = 0; i < n; ++i ) |
|
{ |
|
CUtlVector< CDmxElement* > &array = m_ArrayAttributes[i].m_pAttribute->GetArrayForEdit<CDmxElement*>(); |
|
|
|
if ( m_ArrayAttributes[i].m_nType == AT_ELEMENT ) |
|
{ |
|
CDmxElement *pElement = GetElement( m_ArrayAttributes[i].m_hElement ); |
|
array.AddToTail( pElement ); |
|
} |
|
else |
|
{ |
|
// search id->handle table (both loaded and unloaded) for id, and if not found, create a new handle, map it to the id and return it |
|
DmxElementDictHandle_t hElement = FindElement( m_ArrayAttributes[i].m_ObjectId ); |
|
CDmxElement *pElement = GetElement( hElement ); |
|
array.AddToTail( pElement ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Hook up all element references (which were unserialized as object ids) |
|
//----------------------------------------------------------------------------- |
|
void CDmxElementDictionary::HookUpElementReferences() |
|
{ |
|
HookUpElementArrayAttributes(); |
|
HookUpElementAttributes(); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserialization class for Key Values 2 |
|
//----------------------------------------------------------------------------- |
|
class CDmxSerializerKeyValues2 |
|
{ |
|
public: |
|
bool Unserialize( const char *pFileName, CUtlBuffer &buf, CDmxElement **ppRoot ); |
|
bool Serialize( CUtlBuffer &buf, CDmxElement *pRoot, const char *pFileName ); |
|
|
|
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 unserialization |
|
void EatWhitespacesAndComments( CUtlBuffer &buf ); |
|
TokenType_t ReadToken( CUtlBuffer &buf, CUtlBuffer &token ); |
|
DmxElementDictHandle_t CreateDmxElement( const char *pElementType ); |
|
bool UnserializeAttributeValueFromToken( CDmxAttribute *pAttribute, DmAttributeType_t type, CUtlBuffer &tokenBuf ); |
|
bool UnserializeElementAttribute( CUtlBuffer &buf, DmxElementDictHandle_t hElement, const char *pAttributeName, const char *pElementType ); |
|
bool UnserializeElementArrayAttribute( CUtlBuffer &buf, DmxElementDictHandle_t hElement, const char *pAttributeName ); |
|
bool UnserializeArrayAttribute( CUtlBuffer &buf, DmxElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ); |
|
bool UnserializeAttribute( CUtlBuffer &buf, DmxElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ); |
|
bool UnserializeElement( CUtlBuffer &buf, const char *pElementType, DmxElementDictHandle_t *pHandle ); |
|
bool UnserializeElement( CUtlBuffer &buf, DmxElementDictHandle_t *pHandle ); |
|
|
|
// Methods related to serialization |
|
void SerializeArrayAttribute( CUtlBuffer& buf, CDmxAttribute *pAttribute ); |
|
void SerializeElementAttribute( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxAttribute *pAttribute ); |
|
void SerializeElementArrayAttribute( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxAttribute *pAttribute ); |
|
bool SerializeAttributes( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxElement *pElement ); |
|
bool SaveElement( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxElement *pElement, bool bWriteDelimiters = true ); |
|
|
|
// For unserialization |
|
CDmxElementDictionary m_ElementDict; |
|
DmxElementDictHandle_t m_hRoot; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Serializes a single element attribute |
|
//----------------------------------------------------------------------------- |
|
void CDmxSerializerKeyValues2::SerializeElementAttribute( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxAttribute *pAttribute ) |
|
{ |
|
CDmxElement *pElement = pAttribute->GetValue< CDmxElement* >(); |
|
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_pAttributeTypeName[ AT_ELEMENT ] ); |
|
if ( pElement ) |
|
{ |
|
::Serialize( buf, pElement->GetId() ); |
|
} |
|
buf.PutChar( '\"' ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Serializes an array element attribute |
|
//----------------------------------------------------------------------------- |
|
void CDmxSerializerKeyValues2::SerializeElementArrayAttribute( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxAttribute *pAttribute ) |
|
{ |
|
const CUtlVector<CDmxElement*> &array = pAttribute->GetArray< CDmxElement* >(); |
|
|
|
buf.Printf( "\n[\n" ); |
|
buf.PushTab(); |
|
|
|
int nCount = array.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
CDmxElement *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 = g_pAttributeTypeName[ 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 CDmxSerializerKeyValues2::SerializeArrayAttribute( CUtlBuffer& buf, CDmxAttribute *pAttribute ) |
|
{ |
|
int nCount = pAttribute->GetArrayCount(); |
|
|
|
buf.PutString( "\n[\n" ); |
|
buf.PushTab(); |
|
|
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( pAttribute->GetType() != AT_STRING_ARRAY ) |
|
{ |
|
buf.PutChar( '\"' ); |
|
buf.PushTab(); |
|
} |
|
|
|
pAttribute->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 |
|
//----------------------------------------------------------------------------- |
|
static int SortAttributeByName(const void *p1, const void *p2 ) |
|
{ |
|
const CDmxAttribute **ppAtt1 = (const CDmxAttribute**)p1; |
|
const CDmxAttribute **ppAtt2 = (const CDmxAttribute**)p2; |
|
const char *pAttName1 = (*ppAtt1)->GetName(); |
|
const char *pAttName2 = (*ppAtt2)->GetName(); |
|
return Q_stricmp( pAttName1, pAttName2 ); |
|
} |
|
|
|
bool CDmxSerializerKeyValues2::SerializeAttributes( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxElement *pElement ) |
|
{ |
|
int nCount = pElement->AttributeCount(); |
|
CDmxAttribute **ppAttributes = (CDmxAttribute**)stackalloc( nCount * sizeof(CDmxAttribute*) ); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
ppAttributes[i] = pElement->GetAttribute( i ); |
|
} |
|
|
|
// Sort by name |
|
qsort( ppAttributes, nCount, sizeof(CDmxAttribute*), SortAttributeByName ); |
|
|
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
CDmxAttribute *pAttribute = ppAttributes[ i ]; |
|
|
|
const char *pName = pAttribute->GetName( ); |
|
DmAttributeType_t nAttrType = pAttribute->GetType(); |
|
if ( nAttrType != AT_ELEMENT ) |
|
{ |
|
buf.Printf( "\"%s\" \"%s\" ", pName, g_pAttributeTypeName[ 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 CDmxSerializerKeyValues2::SaveElement( CUtlBuffer& buf, CDmxSerializationDictionary &dict, CDmxElement *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.Printf( "\"id\" \"%s\" ", g_pAttributeTypeName[ AT_OBJECTID ] ); |
|
buf.PutChar( '\"' ); |
|
::Serialize( buf, pElement->GetId() ); |
|
buf.PutString( "\"\n" ); |
|
|
|
SerializeAttributes( buf, dict, pElement ); |
|
|
|
buf.PopTab(); |
|
if ( bWriteDelimiters ) |
|
{ |
|
buf.Printf( "}\n" ); |
|
} |
|
return true; |
|
} |
|
|
|
bool CDmxSerializerKeyValues2::Serialize( CUtlBuffer &outBuf, CDmxElement *pRoot, const char *pFormatName ) |
|
{ |
|
SetSerializationDelimiter( GetCStringCharConversion() ); |
|
SetSerializationArrayDelimiter( "," ); |
|
|
|
bool bFlatMode = !Q_stricmp( pFormatName, "keyvalues2_flat" ); |
|
|
|
// Save elements, attribute links |
|
CDmxSerializationDictionary dict; |
|
dict.BuildElementList( pRoot, bFlatMode ); |
|
|
|
// Save elements to buffer |
|
DmxSerializationHandle_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 CDmxSerializerKeyValues2::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 ( !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 |
|
//----------------------------------------------------------------------------- |
|
CDmxSerializerKeyValues2::TokenType_t CDmxSerializerKeyValues2::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; |
|
} |
|
|
|
// Point the token buffer to the token + update the original buffer get index |
|
token.SetExternalBuffer( (void*)buf.PeekGet(), nLength, nLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); |
|
buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 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 |
|
//----------------------------------------------------------------------------- |
|
DmxElementDictHandle_t CDmxSerializerKeyValues2::CreateDmxElement( const char *pElementType ) |
|
{ |
|
// See if we can create an element of that type |
|
CDmxElement *pElement = new CDmxElement( pElementType ); |
|
return m_ElementDict.InsertElement( pElement ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reads an attribute for an element |
|
//----------------------------------------------------------------------------- |
|
bool CDmxSerializerKeyValues2::UnserializeElementAttribute( CUtlBuffer &buf, DmxElementDictHandle_t hElement, const char *pAttributeName, const char *pElementType ) |
|
{ |
|
CDmxElement *pElement = m_ElementDict.GetElement( hElement ); |
|
if ( pElement->HasAttribute( pAttributeName ) ) |
|
{ |
|
g_KeyValues2ErrorStack.ReportError( "Attribute \"%s\" was defined more than once.\n", pAttributeName ); |
|
return false; |
|
} |
|
|
|
CDmxAttribute *pAttribute; |
|
{ |
|
CDmxElementModifyScope modify( pElement ); |
|
pAttribute = pElement->AddAttribute( pAttributeName ); |
|
} |
|
|
|
DmxElementDictHandle_t h; |
|
bool bOk = UnserializeElement( buf, pElementType, &h ); |
|
if ( bOk ) |
|
{ |
|
CDmxElement *pNewElement = m_ElementDict.GetElement( h ); |
|
pAttribute->SetValue( pNewElement ); |
|
} |
|
return bOk; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reads an attribute for an element array |
|
//----------------------------------------------------------------------------- |
|
bool CDmxSerializerKeyValues2::UnserializeElementArrayAttribute( CUtlBuffer &buf, DmxElementDictHandle_t hElement, const char *pAttributeName ) |
|
{ |
|
CDmxElement *pElement = m_ElementDict.GetElement( hElement ); |
|
if ( pElement->HasAttribute( pAttributeName ) ) |
|
{ |
|
g_KeyValues2ErrorStack.ReportError( "Attribute \"%s\" was defined more than once.\n", pAttributeName ); |
|
return false; |
|
} |
|
|
|
CDmxAttribute *pAttribute; |
|
{ |
|
CDmxElementModifyScope modify( pElement ); |
|
pAttribute = pElement->AddAttribute( pAttributeName ); |
|
} |
|
|
|
// Arrays first must have a '[' specified |
|
TokenType_t token; |
|
CUtlBuffer tokenBuf; |
|
CUtlCharConversion *pConv; |
|
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 |
|
pConv = GetCStringCharConversion(); |
|
int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); |
|
char *pElementType = (char*)stackalloc( nLength * sizeof(char) ); |
|
tokenBuf.GetDelimitedString( pConv, pElementType, nLength ); |
|
|
|
// Use the element type to figure out if we're using a element reference or an inlined element |
|
if ( !Q_strncmp( pElementType, g_pAttributeTypeName[AT_ELEMENT], nLength ) ) |
|
{ |
|
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 |
|
pConv = GetCStringCharConversion(); |
|
nLength = tokenBuf.PeekDelimitedStringLength( pConv ); |
|
char *pElementId = (char*)stackalloc( nLength * sizeof(char) ); |
|
tokenBuf.GetDelimitedString( pConv, pElementId, nLength ); |
|
|
|
DmObjectId_t id; |
|
if ( !UniqueIdFromString( &id, pElementId ) ) |
|
{ |
|
g_KeyValues2ErrorStack.ReportError( "Encountered invalid element ID data!" ); |
|
return false; |
|
} |
|
|
|
Assert( IsUniqueIdValid( id ) ); |
|
m_ElementDict.AddArrayAttribute( pAttribute, id ); |
|
} |
|
else |
|
{ |
|
DmxElementDictHandle_t hArrayElement; |
|
bool bOk = UnserializeElement( buf, pElementType, &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 CDmxSerializerKeyValues2::UnserializeAttributeValueFromToken( CDmxAttribute *pAttribute, DmAttributeType_t type, 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 = ( type == AT_STRING ) || ( type == 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 ( type < AT_FIRST_ARRAY_TYPE ) |
|
{ |
|
bOk = pAttribute->Unserialize( type, buf ); |
|
} |
|
else |
|
{ |
|
bOk = pAttribute->UnserializeElement( type, buf ); |
|
} |
|
|
|
if ( bIsString ) |
|
{ |
|
SetSerializationDelimiter( NULL ); |
|
} |
|
|
|
return bOk; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reads an attribute for an element array |
|
//----------------------------------------------------------------------------- |
|
bool CDmxSerializerKeyValues2::UnserializeArrayAttribute( CUtlBuffer &buf, DmxElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ) |
|
{ |
|
CDmxElement *pElement = m_ElementDict.GetElement( hElement ); |
|
if ( pElement->HasAttribute( pAttributeName ) ) |
|
{ |
|
g_KeyValues2ErrorStack.ReportError( "Encountered duplicate attribute definition for attribute \"%s\"!", pAttributeName ); |
|
return false; |
|
} |
|
|
|
CDmxAttribute *pAttribute; |
|
{ |
|
CDmxElementModifyScope modify( pElement ); |
|
pAttribute = pElement->AddAttribute( pAttributeName ); |
|
} |
|
|
|
// Arrays first must have a '[' specified |
|
TokenType_t token; |
|
CUtlBuffer tokenBuf; |
|
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, nAttrType, 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 CDmxSerializerKeyValues2::UnserializeAttribute( CUtlBuffer &buf, |
|
DmxElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ) |
|
{ |
|
// Read the attribute value |
|
CUtlBuffer tokenBuf; |
|
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; |
|
} |
|
|
|
CDmxElement *pElement = m_ElementDict.GetElement( hElement ); |
|
if ( ( nAttrType == AT_OBJECTID ) && !Q_strnicmp( pAttributeName, "id", 3 ) ) |
|
{ |
|
CUtlCharConversion *pConv = GetCStringCharConversion(); |
|
int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); |
|
char *pElementId = (char*)stackalloc( nLength * sizeof(char) ); |
|
tokenBuf.GetDelimitedString( pConv, pElementId, nLength ); |
|
|
|
DmObjectId_t id; |
|
if ( !UniqueIdFromString( &id, pElementId ) ) |
|
{ |
|
g_KeyValues2ErrorStack.ReportError( "Encountered invalid element ID data!" ); |
|
return false; |
|
} |
|
|
|
m_ElementDict.SetElementId( hElement, id ); |
|
pElement->SetId( id ); |
|
return true; |
|
} |
|
|
|
if ( pElement->HasAttribute( pAttributeName ) ) |
|
{ |
|
g_KeyValues2ErrorStack.ReportError( "Encountered duplicate attribute definition for attribute \"%s\"!", pAttributeName ); |
|
return false; |
|
} |
|
|
|
CDmxAttribute *pAttribute; |
|
{ |
|
CDmxElementModifyScope modify( pElement ); |
|
pAttribute = pElement->AddAttribute( pAttributeName ); |
|
} |
|
|
|
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, nAttrType, tokenBuf ) ) |
|
return true; |
|
|
|
g_KeyValues2ErrorStack.ReportError("Error reading attribute \"%s\"", pAttributeName ); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserializes a single element given the type name |
|
//----------------------------------------------------------------------------- |
|
bool CDmxSerializerKeyValues2::UnserializeElement( CUtlBuffer &buf, const char *pElementType, DmxElementDictHandle_t *pHandle ) |
|
{ |
|
*pHandle = ELEMENT_DICT_HANDLE_INVALID; |
|
|
|
// Create the element |
|
DmxElementDictHandle_t hElement = CreateDmxElement( pElementType ); |
|
|
|
// Report errors relative to this type name |
|
CKeyValues2ErrorContext errorReport( pElementType ); |
|
|
|
TokenType_t token; |
|
CUtlBuffer tokenBuf; |
|
CUtlCharConversion *pConv; |
|
int nLength; |
|
|
|
// 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 |
|
pConv = GetCStringCharConversion(); |
|
nLength = tokenBuf.PeekDelimitedStringLength( pConv ); |
|
char *pAttributeName = (char*)stackalloc( nLength * sizeof(char) ); |
|
tokenBuf.GetDelimitedString( pConv, pAttributeName, nLength ); |
|
|
|
// 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!", pAttributeName ); |
|
return false; |
|
} |
|
|
|
pConv = GetCStringCharConversion(); |
|
nLength = tokenBuf.PeekDelimitedStringLength( pConv ); |
|
char *pAttributeType = (char*)stackalloc( nLength * sizeof(char) ); |
|
tokenBuf.GetDelimitedString( pConv, pAttributeType, nLength ); |
|
|
|
DmAttributeType_t nAttrType = AT_UNKNOWN; |
|
for ( int i = 0; i < AT_TYPE_COUNT; ++i ) |
|
{ |
|
if ( !Q_stricmp( g_pAttributeTypeName[i], pAttributeType ) ) |
|
{ |
|
nAttrType = (DmAttributeType_t)i; |
|
break; |
|
} |
|
} |
|
|
|
// Next, read an attribute value |
|
bool bOk = true; |
|
switch( nAttrType ) |
|
{ |
|
case AT_UNKNOWN: |
|
bOk = UnserializeElementAttribute( buf, hElement, pAttributeName, pAttributeType ); |
|
break; |
|
|
|
case AT_ELEMENT_ARRAY: |
|
bOk = UnserializeElementArrayAttribute( buf, hElement, pAttributeName ); |
|
break; |
|
|
|
default: |
|
if ( nAttrType >= AT_FIRST_ARRAY_TYPE ) |
|
{ |
|
bOk = UnserializeArrayAttribute( buf, hElement, pAttributeName, nAttrType ); |
|
} |
|
else |
|
{ |
|
bOk = UnserializeAttribute( buf, hElement, pAttributeName, nAttrType ); |
|
} |
|
break; |
|
} |
|
|
|
if ( !bOk ) |
|
return false; |
|
} |
|
|
|
*pHandle = hElement; |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserializes a single element |
|
//----------------------------------------------------------------------------- |
|
bool CDmxSerializerKeyValues2::UnserializeElement( CUtlBuffer &buf, DmxElementDictHandle_t *pHandle ) |
|
{ |
|
*pHandle = ELEMENT_DICT_HANDLE_INVALID; |
|
|
|
// First, read the type name |
|
CUtlBuffer tokenBuf; |
|
CUtlCharConversion* pConv; |
|
|
|
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; |
|
} |
|
|
|
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 CDmxSerializerKeyValues2::Unserialize( const char *pFileName, CUtlBuffer &buf, CDmxElement **ppRoot ) |
|
{ |
|
*ppRoot = NULL; |
|
|
|
g_KeyValues2ErrorStack.SetFilename( pFileName ); |
|
m_hRoot = ELEMENT_DICT_HANDLE_INVALID; |
|
m_ElementDict.Clear(); |
|
|
|
bool bOk = true; |
|
while ( buf.IsValid() ) |
|
{ |
|
DmxElementDictHandle_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 ); |
|
m_ElementDict.Clear(); |
|
|
|
if ( !bOk ) |
|
{ |
|
CleanupDMX( *ppRoot ); |
|
*ppRoot = NULL; |
|
} |
|
|
|
return bOk; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserialization entry point for text files (assumes version has been stripped) |
|
//----------------------------------------------------------------------------- |
|
bool UnserializeTextDMX( const char *pFileName, CUtlBuffer &buf, CDmxElement **ppRoot ) |
|
{ |
|
CDmxSerializerKeyValues2 dmxUnserializer; |
|
return dmxUnserializer.Unserialize( pFileName, buf, ppRoot ); |
|
} |
|
|
|
|
|
bool SerializeTextDMX( const char *pFileName, CUtlBuffer &buf, CDmxElement *pRoot ) |
|
{ |
|
CDmxSerializerKeyValues2 dmxSerializer; |
|
return dmxSerializer.Serialize( buf, pRoot, pFileName ); |
|
}
|
|
|