1 
2 /**
3 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
4 
5 DO NOT CHANGE THIS FILE!
6 
7 this file is deprecated and will be replaced with
8 
9 lsl/battle/tdfcontainer.cpp
10 
11 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12 **/
13 
14 
15 
16 #include "tdfcontainer.h"
17 
18 #include "utils/conversion.h"
19 #include "utils/debug.h"
20 #include <wx/intl.h>
21 #include <wx/tokenzr.h>
22 #include <wx/log.h>
23 #include <cmath>
24 
TDFWriter(wxString & s)25 TDFWriter::TDFWriter( wxString &s ):
26 		m_stream( s ),
27 		m_depth( 0 )
28 {
29 
30 }
31 
~TDFWriter()32 TDFWriter::~TDFWriter() {
33 	Close();
34 }
EnterSection(const wxString & name)35 void TDFWriter::EnterSection( const wxString &name ) {
36 	Indent();
37 	m_stream << _T( "[" ) << name << _T( "]\n" );
38 	Indent();
39 	m_stream << _T( "{\n" );
40 	m_depth++;
41 }
LeaveSection()42 void TDFWriter::LeaveSection() {
43 	m_depth--;
44 	Indent();
45 	m_stream << _T( "}\n" );
46 }
Indent()47 void TDFWriter::Indent() {
48 	for ( int i = 0;i < m_depth;++i )m_stream << _T( "\t" );
49 }
50 //wxString GetCurrentPath();
Append(const wxString & name,wxString value)51 void TDFWriter::Append( const wxString &name, wxString value ) {
52 	Indent();
53 	m_stream << name << _T( "=" ) << value << _T( ";\n" );
54 }
55 
Close()56 void TDFWriter::Close() {
57 	while ( m_depth > 0 )
58 		LeaveSection();
59 	if ( m_depth < 0 ) {
60 		wxLogWarning( _T( "error in TDFWriter usage: more LeaveSection() calls than EnterSection(). Please contact springlobby developers" ) );
61 	}
62 }
63 
AppendLineBreak()64 void TDFWriter::AppendLineBreak() {
65 	m_stream << _T( "\n" );
66 }
67 
68 #include "sstream"
69 #include "fstream"
70 #include <iomanip>
71 #include <algorithm>
72 
73 
ReportError(const Token & t,const wxString & err)74 void Tokenizer::ReportError( const Token &t, const wxString &err ) {
75 	wxLogMessage( _T( "TDF parsing error at (%s), on token \"%s\" : %s" ), t.pos_string.c_str(), t.value_s.c_str(), err.c_str() );
76 	errors++;
77 }
78 
79 //#define ReportError(a) {std::cerr<<"error "<<(a);}
80 //#define ReportError(a) {wxLogMessage(_T("error %s"),TowxString(a).c_str());}
81 
EnterStream(std::istream & stream_,const wxString & name)82 void Tokenizer::EnterStream( std::istream &stream_, const wxString &name ) {
83 	skip_eol = false;
84 	include_stack.push_back( IncludeCacheEntry( &stream_, false ) );
85 	include_stack.back().name = name;
86 }
87 
UnwindStack()88 void Tokenizer::UnwindStack() {
89 	while ( ( !include_stack.empty() ) && include_stack.back().stream && ( !include_stack.back().stream->good() ) ) {
90 		include_stack.pop_back();
91 	}
92 }
93 
PeekNextChar()94 char Tokenizer::PeekNextChar() {
95 	UnwindStack();
96 	//std::string tmp;
97 	char tmp = 0;
98 	if ( !include_stack.empty() )tmp = ( *include_stack.back().stream ).peek();
99 	return tmp;
100 }
101 
GetNextChar()102 char Tokenizer::GetNextChar() {
103 	UnwindStack();
104 	//std::string tmp;
105 	if ( include_stack.empty() )return 0;
106 
107 	char c = ( *include_stack.back().stream ).get();
108 
109 	if ( ( !skip_eol ) && ( c == 10 || c == 13 ) ) {// end of line
110 		include_stack.back().line += 1;
111 		include_stack.back().column = 1;
112 
113 		// if next is different we'll skip it.
114 		//std::istream &stream=(*include_stack.back().stream);
115 		char nc = ( *include_stack.back().stream ).peek();
116 		if ( ( nc == 10 || nc == 13 ) && ( nc != c ) )skip_eol = true;
117 	} else {
118 		if ( !skip_eol )include_stack.back().column += 1;
119 		skip_eol = false;
120 	}
121 
122 	return c;
123 }
124 
Good()125 bool Tokenizer::Good() {
126 	UnwindStack();
127 	return !include_stack.empty();
128 }
129 
ReadToken(Token & token)130 void Tokenizer::ReadToken( Token &token ) {
131 start:
132 
133 	SkipSpaces();
134 
135 
136 	token.value_s.clear();
137 
138 	if ( !Good() ) {
139 		token.type = Token::type_eof;
140 		token.pos_string = _T( "EOF" );
141 		return;
142 	}
143 
144 	token.pos_string = wxString();
145 	if ( !include_stack.empty() && !include_stack.back().name.empty() )token.pos_string << include_stack.back().name << _T( " , " );
146 	token.pos_string << _( "line " ) << include_stack.back().line << _( " , column " ) << include_stack.back().column;
147 
148 	char c = GetNextChar();
149 	token.value_s += c;
150 	// first find what token is it, and handle all except numbers
151 	switch ( c ) {
152 		case '[': {
153 				token.type = Token::type_section_name;
154 				token.value_s.clear();
155 				bool skip_next_eol_char = false;
156 				while ( Good() ) {
157 					c = GetNextChar();
158 					// wxString has problem with zero characters, replace by space.
159 					if ( c == 0 ) {
160 						c = ' ';
161 					}
162 					if ( c == '\\' ) {
163 						if ( Good() ) {
164 							token.value_s += GetNextChar();
165 						} else {
166 							ReportError( token, wxT( "Quotes not closed before end of file" ) );
167 							return;
168 						}
169 					} else if ( c == ']' ) {
170 						return;
171 					} else
172 					{
173 						token.value_s += c;
174 					}
175 					// handle end of line
176 					if ( skip_next_eol_char ) {
177 						skip_next_eol_char = false;
178 					} else if ( c == 10 || c == 13 ) {
179 						//++line;
180 						//column=1;
181 						if ( ( PeekNextChar() == 10 || PeekNextChar() == 13 ) && ( PeekNextChar() != c ) )skip_next_eol_char = true;
182 					}
183 				}
184 				ReportError( token, wxT( "Quotes not closed before end of file" ) );
185 			}
186 		case '{':
187 			token.type = Token::type_enter_section;
188 			return;
189 		case '}':
190 			token.type = Token::type_leave_section;
191 			return;
192 		case ';':
193 			token.type = Token::type_semicolon;
194 			return;
195 		case '=':
196 			token.type = Token::type_entry_value;
197 			token.value_s = wxEmptyString;
198 			while ( Good() && PeekNextChar() != ';' ) {
199 				unsigned char c_ = GetNextChar();
200 				token.value_s += c_;
201 			}
202 			return;
203 		case '/':// handle comments
204 			if ( PeekNextChar() == '/' ) {
205 				//SkipToEOL();
206 				if ( !include_stack.empty() ) {
207 					std::string tmp;
208 					std::getline( ( *include_stack.back().stream ), tmp );
209 					include_stack.back().line += 1;
210 					include_stack.back().column = 1;
211 				}
212 
213 				goto start;
214 			}
215 			else if ( PeekNextChar() == '*' ) {// multi-line comment
216 				while ( Good() ) {
217 					char c1 = GetNextChar();
218 					if ( ( c1 == '*' ) && ( PeekNextChar() == '/' ) ) {
219 						GetNextChar();
220 						break;
221 					}
222 				}
223 				goto start;
224 			}
225 		default:
226 			while ( Good() && PeekNextChar() != '=' ) {
227 				unsigned char c_ = GetNextChar();
228 				token.value_s += c_;
229 			}
230 			token.type = Token::type_entry_name;
231 			return;
232 	}
233 }
234 
SkipSpaces()235 void Tokenizer::SkipSpaces() {
236 	while ( Good() && IsWhitespace( PeekNextChar() ) ) {
237 		GetNextChar();
238 	}
239 }
240 
GetToken(int i)241 Token Tokenizer::GetToken( int i ) {
242 	int p = buffer_pos + i;
243 	if ( p < 0 )return Token();
244 	while ( int( token_buffer.size() ) < p + 1 ) {
245 		Token t;
246 		ReadToken( t );
247 		if ( t.IsEOF() )return t;
248 		token_buffer.push_back( t );
249 	}
250 
251 	return token_buffer[p];
252 }
253 
Step(int i)254 void Tokenizer::Step( int i ) {
255 	buffer_pos += i;
256 }
257 namespace SL {
~Node()258 Node::~Node() {
259 	//if(parent)parent->Remove(name);
260 }
261 
Parent() const262 DataList* Node::Parent() const { // parent list
263 	return parent;
264 }
265 
IsChildOf(DataList * what) const266 bool Node::IsChildOf( DataList *what ) const {
267 	DataList *current = Parent();
268 	while ( current ) {
269 		if ( current == what )return true;
270 		current = current->Parent();
271 	}
272 	return false;
273 }
274 
ListRemove()275 void Node::ListRemove() {
276 	//if(parent->list_first==this)parent->list_first=next;
277 	//if(parent->list_last==this)parent->list_last=prev;
278 	if ( list_prev ) {
279 		list_prev->list_next = this->list_next;
280 	}
281 	if ( list_next ) {
282 		list_next->list_prev = this->list_prev;
283 	}
284 	list_prev = NULL;
285 	list_next = NULL;
286 }
287 
ListInsertAfter(Node * what)288 void Node::ListInsertAfter( Node *what ) {
289 	ListRemove();
290 	if ( !what )return;
291 	this->list_next = what->list_next;
292 	if ( what->list_next )what->list_next->list_prev = this;
293 	what->list_next = this;
294 	this->list_prev = what;
295 }
296 
Name()297 wxString Node::Name() {
298 	return name;
299 }
SetName(const wxString & name_)300 bool Node::SetName( const wxString &name_ ) {
301 	if ( parent ) {
302 		return parent->Rename( name, name_ );
303 	}
304 	name = name_;
305 	return true;
306 }
307 
Save(TDFWriter &)308 void Node::Save( TDFWriter &/*unused*/ ) {
309 	/// nothing to save there.
310 }
Load(Tokenizer &)311 void Node::Load( Tokenizer &/*unused*/ ) {
312 	/// nothing to load there.
313 	//ASSERT_LOGIC(0,_T("this function should not be called."));
314 }
315 
316 /// ***********************************************************
317 ///  class DataList
318 /// ***********************************************************
319 
DataList()320 DataList::DataList() {
321 	//parent = NULL;
322 	list_loop.list_prev = &list_loop;
323 	list_loop.list_next = &list_loop;
324 	list_loop.parent = this;
325 	list_loop.Reference();
326 }
327 
~DataList()328 DataList::~DataList() {// disconnect from childs
329 	PNode node = First();
330 	while ( node.Ok() && node != End() ) {
331 		//std::cout<<"printing"<<std::endl;
332 		PNode nextnode = Next( node );
333 		node->parent = NULL;
334 		node->ListRemove();
335 		node = nextnode;
336 	}
337 }
338 
Insert(PNode node)339 bool DataList::Insert( PNode node )/// return false if such entry already exists.
340 {
341 	if ( !node.Ok() )return false;
342 	bool inserted = nodes.insert( std::pair<wxString, PNode>( ( *node ).name.Lower(), node ) ).second;
343 	if ( !inserted )return false;
344 
345 	node->parent = this;
346 	node->ListInsertAfter( list_loop.list_prev );
347 	return true;
348 }
349 
InsertAt(PNode node,PNode where)350 bool DataList::InsertAt( PNode node, PNode where )/// return false if such entry already exists.
351 {
352 	if ( !node.Ok() )return false;
353 	if ( !( where->list_prev ) )return false;
354 	bool inserted = nodes.insert( std::pair<wxString, PNode>( ( *node ).name, node ) ).second;
355 	if ( !inserted )return false;
356 
357 	node->parent = this;
358 	node->ListInsertAfter( where->list_prev );
359 	return true;
360 }
361 
362 #ifdef use_std_string
363 static const char* rename_prefix = "!";
364 #else
365 static const wxChar* rename_prefix = _T( "!" );
366 #endif
367 
InsertRename(PNode node)368 void DataList::InsertRename( PNode node ) {/// rename if such entry already exists. str contains new name.
369 	if ( !node.Ok() )return;
370 
371 	if ( !Insert( node ) ) {
372 		wxString original_name = node->Name();
373 		for ( int n = 0;n < 10000;++n ) {
374 			//wxString tmp=str+wxString(rename_prefix);
375 #ifdef use_std_string
376 			std::ostringstream os;
377 			os << original_name << rename_prefix << n;
378 			node->name = os.str();
379 #else
380 			wxString tmp;
381 			tmp << original_name;
382 			tmp << rename_prefix;
383 			tmp << n;
384 			node->name = tmp;
385 #endif
386 			if ( Insert( node ) ) {
387 				return;
388 			}
389 		}
390 		wxLogError( _T( "insertRename: iterated over 10 000 names, way too many" ) );
391 	}
392 }
393 
InsertRenameAt(PNode node,PNode where)394 void DataList::InsertRenameAt( PNode node, PNode where ) {// rename if such entry already exists. str contains new name.
395 	if ( !node.Ok() )return;
396 	if ( !where->list_prev )return;
397 
398 	if ( !InsertAt( node, where ) ) {
399 		for ( int n = 0;n < 10000;++n ) {
400 
401 #ifdef use_std_string
402 			std::ostringstream os;
403 			os << node->Name() << rename_prefix << n;
404 			node->name = os.str();
405 #else
406 			wxString tmp;
407 			tmp << ( node->Name() );
408 			tmp << rename_prefix;
409 			tmp << n;
410 			node->name = tmp;
411 #endif
412 			if ( InsertAt( node, where ) ) {
413 				return;
414 			}
415 		}
416 		wxLogError( _T( "insertRename: iterated over 10 000 names, way too many" ) );
417 	}
418 }
419 
Remove(const wxString & str)420 bool DataList::Remove( const wxString &str ) {
421 	//PNode node=nodes.find(str.Lower())->last;
422 	PNode node = Find( str );
423 	if ( !node.Ok() )return false;
424 	if ( nodes.erase( str.Lower() ) <= 0 ) return false;
425 
426 	node->parent = NULL;
427 	node->ListRemove();
428 	return true;
429 }
430 
Remove(PNode node)431 bool DataList::Remove( PNode node ) {
432 	if ( !node.Ok() )return false;
433 	if ( nodes.erase( node->Name().Lower() ) <= 0 ) return false;
434 
435 	node->parent = NULL;
436 	node->ListRemove();
437 	return true;
438 }
439 
Rename(const wxString & old_name,const wxString & new_name)440 bool DataList::Rename( const wxString &old_name, const wxString &new_name ) {
441 	// check that new name is not used up.
442 	if ( nodes.find( new_name.Lower() ) != nodes.end() )return false;
443 	nodes_iterator i = nodes.find( old_name.Lower() );
444 	if ( i == nodes.end() )return false;
445 	PNode node = i->second;
446 
447 	ASSERT_LOGIC( node.Ok(), _T( "Internal TDF tree consistency (1)" ) );
448 	ASSERT_LOGIC( node->Name().Lower() == old_name.Lower(), _T( "Internal TDF tree consistency (2)" ) );
449 
450 	node->name = new_name.Lower();
451 	nodes.erase( i );
452 	bool inserted = nodes.insert( std::pair<wxString, PNode>( ( *node ).name.Lower(), node ) ).second;
453 	ASSERT_LOGIC( inserted, _T( "DataList::Rename failed" ) );
454 	return inserted;
455 }
456 
457 /// find by name. unused.
Find(const wxString & str)458 PNode DataList::Find( const wxString &str ) {
459 	if ( str == _T( ".." ) )return Parent();
460 	if ( str == _T( "." ) )return this;
461 	nodes_iterator i = nodes.find( str.Lower() );
462 	if ( i != nodes.end() ) {
463 		ASSERT_LOGIC( i->second->Name().Lower() == str.Lower(), _T( "Internal TDF tree consistency (3)" ) );
464 		return i->second;
465 	}
466 	return NULL;
467 }
468 
Path()469 wxString DataList::Path() {
470 	wxString result;
471 	PDataList tmp( this );
472 	while ( tmp.Ok() ) {
473 		result = wxString( _T( "/" ) ) + tmp->Name();
474 		tmp = tmp->Parent();
475 	}
476 	return name;
477 }
478 
FindByPath(const wxString & str)479 PNode DataList::FindByPath( const wxString &str ) {
480 	if ( str.empty() )return this;
481 	int i = 0;
482 	wxString buff;
483 	PDataList current_dir( this );
484 	if ( str[i] == '/' ) {// go to root
485 		PDataList tmp = Parent();
486 		while ( tmp.Ok() ) {
487 			current_dir = tmp;
488 			tmp = tmp->Parent();
489 		}
490 	}
491 	else {
492 		buff += str[0];
493 
494 	}
495 	i = 1;
496 	while ( ( unsigned int )( i ) < str.size() ) {
497 		if ( str[i] == '/' ) {
498 			if ( buff == _T( ".." ) ) {
499 				current_dir = current_dir->Parent();
500 				if ( !current_dir.Ok() )
501 					return NULL;
502 			}
503 			else if ( buff != _T( "." ) && !buff.empty() ) {//
504 				PNode node = current_dir->Find( buff );
505 				if ( !node.Ok() )
506 					return NULL;
507 				PDataList datalist( node );
508 				if ( datalist.Ok() ) {
509 					current_dir = datalist;
510 				}
511 				else
512 					return NULL;
513 			}
514 			buff = wxEmptyString;
515 		}
516 		else {
517 			buff += str[i];
518 		}
519 		++i;
520 	}
521 	if ( current_dir.Ok() ) {
522 		if ( !buff.empty() ) {
523 			return current_dir->Find( buff );
524 		}
525 		else
526 			return PNode( current_dir );
527 	}
528 	else {
529 		return NULL;
530 	}
531 }
532 
Next(PNode what)533 PNode DataList::Next( PNode what ) {
534 	if ( what.Ok() )return what->list_next;
535 	return NULL;
536 }
Prev(PNode what)537 PNode DataList::Prev( PNode what ) {
538 	if ( what.Ok() )return what->list_prev;
539 	return NULL;
540 }
End()541 PNode DataList::End() {
542 	return PNode( &list_loop );
543 }
First()544 PNode DataList::First() {
545 	return PNode( list_loop.list_next );
546 }
Last()547 PNode DataList::Last() {
548 	return PNode( list_loop.list_prev );
549 }
550 
551 /*
552 void DataList::PrintContent(std::ostream &s) {
553     PNode node=First();
554 
555     while(node.Ok() && node!=End()){
556       //std::cout<<"printing"<<std::endl;
557       #ifdef use_std_string
558       s<<std::endl<<"'"<<node->Name()<<"'={";
559         node->PrintContent(s);
560         s<<"}"<<std::endl;
561       #else
562         s<<std::endl<<"'"<<node->Name().mb_str()<<"'={";
563         node->PrintContent(s);
564         s<<"}"<<std::endl;
565       #endif
566       node=Next(node);
567     }
568 }*/
Save(TDFWriter & f)569 void DataList::Save( TDFWriter &f ) {
570 	PNode node = First();
571 	if ( node == End() )return;
572 
573 	while ( node.Ok() && node != End() ) {
574 		// if class name is not set properly, continue
575 		//if(node->ClassName().empty())continue;
576 		PDataList list( node );
577 		if ( list.Ok() ) {
578 			f.EnterSection( list->Name() );
579 			list->Save( f );
580 			f.LeaveSection();
581 		} else {
582 			PDataLeaf leaf( node );
583 			if ( leaf.Ok() ) {
584 				leaf->Save( f );
585 			}
586 		}
587 		node = Next( node );
588 	}
589 
590 }
591 
Load(Tokenizer & f)592 void DataList::Load( Tokenizer &f ) {
593 	while ( f.Good() ) {
594 		Token t = f.TakeToken();
595 		switch ( t.type ) {
596 			case Token::type_leave_section:
597 			case Token::type_eof:
598 				return;
599 			case Token::type_entry_name:
600 				{
601 					PDataLeaf new_leaf( new DataLeaf );
602 					new_leaf->SetName( t.value_s );
603 					new_leaf->Load( f );
604 					Insert( PNode( new_leaf ) );
605 				}
606 				break;
607 
608 			case Token::type_section_name:
609 				{
610 					Token t2 = f.TakeToken();
611 					if ( t2.type != Token::type_enter_section ) {
612 						f.ReportError( t, _T( "'{' expected" ) );
613 					} else {
614 						PDataList new_list( new DataList );
615 						new_list->SetName( t.value_s );
616 						new_list->Load( f );// will eat the '}'
617 						Insert( PNode( new_list ) );
618 					}
619 				}
620 				break;
621 			default:
622 				f.ReportError( t, _T( "[sectionname] or entryname= expected." ) );
623 		}
624 
625 	}
626 }
627 
628 
GetInt(const wxString & f_name,int default_value,bool * it_worked)629 int DataList::GetInt( const wxString &f_name, int default_value, bool *it_worked ) {
630 	PDataLeaf leaf( Find( f_name ) );
631 	if ( !leaf.ok() ) {
632 		if ( it_worked ) {
633 			*it_worked = false;
634 		}
635 		return default_value;
636 	}
637 	wxString s = leaf->GetValue();
638 	long result = default_value;
639 	if ( !s.ToLong( &result ) ) {
640 		if ( it_worked ) {
641 			*it_worked = false;
642 		}
643 		return result;
644 	}
645 	if ( it_worked ) {
646 		*it_worked = true;
647 	}
648 	return result;
649 }
GetDouble(const wxString & f_name,double default_value,bool * it_worked)650 double DataList::GetDouble( const wxString &f_name, double default_value, bool *it_worked ) {
651 	PDataLeaf leaf( Find( f_name ) );
652 	if ( !leaf.ok() ) {
653 		if ( it_worked ) {
654 			*it_worked = false;
655 		}
656 		return default_value;
657 	}
658 	wxString s = leaf->GetValue();
659 	double result = default_value;
660 	if ( !s.ToDouble( &result ) ) {
661 		if ( it_worked ) {
662 			*it_worked = false;
663 		}
664 		return result;
665 	}
666 	if ( it_worked ) {
667 		*it_worked = true;
668 	}
669 	return result;
670 
671 }
GetString(const wxString & f_name,const wxString & default_value,bool * it_worked)672 wxString DataList::GetString( const wxString &f_name, const wxString &default_value, bool *it_worked ) {
673 	PDataLeaf leaf( Find( f_name ) );
674 	if ( !leaf.ok() ) {
675 		if ( it_worked ) {
676 			*it_worked = false;
677 		}
678 		return default_value;
679 	}
680 	if ( it_worked ) {
681 		*it_worked = true;
682 	}
683 	return leaf->GetValue();
684 }
GetDoubleArray(const wxString & f_name,int n_values,double * values)685 int DataList::GetDoubleArray( const wxString &f_name, int n_values, double *values ) {
686 	PDataLeaf leaf( Find( f_name ) );
687 	if ( !leaf.ok() ) {
688 		return 0;
689 	}
690 	wxStringTokenizer tok( leaf->GetValue() );
691 	int i = 0;
692 	int values_read = 0;
693 	for ( i = 0;i < n_values && tok.HasMoreTokens();++i ) {
694 		wxString s = tok.GetNextToken();
695 		if ( s.ToDouble( values + i ) )values_read++;
696 	}
697 	return values_read;
698 }
699 
GetColour(const wxString & f_name,const wxColour & default_value,bool * it_worked)700 wxColour DataList::GetColour( const wxString &f_name, const wxColour &default_value, bool *it_worked ) {
701 	double values[3];
702 	if ( GetDoubleArray( f_name, 3, values ) != 3 ) {
703 		if ( it_worked ) {
704 			*it_worked = false;
705 		}
706 		return default_value;
707 	}
708 	if ( it_worked ) {
709 		*it_worked = true;
710 	}
711 	return wxColour( values[0]*255.99, values[1]*255.99, values[2]*255.99 );
712 }
713 
714 
GetValue()715 wxString DataLeaf::GetValue() {
716 	return value;
717 }
SetValue(const wxString & value_)718 void DataLeaf::SetValue( const wxString &value_ ) {
719 	value = value_;
720 }
721 
Save(TDFWriter & f)722 void DataLeaf::Save( TDFWriter &f ) {
723 	f.Append( Name(), GetValue() );
724 }
Load(Tokenizer & f)725 void DataLeaf::Load( Tokenizer &f ) {
726 	Token t = f.TakeToken();
727 	value = t.value_s;
728 	t = f.TakeToken();
729 	if ( t.value_s != _T( ";" ) ) {
730 		f.ReportError( t, _T( "; expected" ) );
731 	}
732 }
733 } // end namespace SL
734 
ParseTDF(std::istream & s,int * error_count)735 SL::PDataList ParseTDF( std::istream &s, int *error_count ) {
736 	Tokenizer t;
737 	t.EnterStream( s );
738 	SL::PDataList result( new SL::DataList );
739 	result->Load( t );
740 	if ( error_count ) {
741 		*error_count = t.NumErrors();
742 	}
743 	return result;
744 }
745