diff options
Diffstat (limited to 'dmxloader/dmxloadertext.cpp')
| -rw-r--r-- | dmxloader/dmxloadertext.cpp | 1442 |
1 files changed, 1442 insertions, 0 deletions
diff --git a/dmxloader/dmxloadertext.cpp b/dmxloader/dmxloadertext.cpp new file mode 100644 index 0000000..ba4ea7f --- /dev/null +++ b/dmxloader/dmxloadertext.cpp @@ -0,0 +1,1442 @@ +//========= 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(); + + 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; +}; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDmxElementDictionary::CDmxElementDictionary() +{ +} + + +//----------------------------------------------------------------------------- +// 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 ); +} |