MezzanineEngine 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
markupparser.cpp
1 //© Copyright 2010 - 2012 BlackTopp Studios Inc.
2 /* This file is part of The Mezzanine Engine.
3 
4  The Mezzanine Engine is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8 
9  The Mezzanine Engine is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with The Mezzanine Engine. If not, see <http://www.gnu.org/licenses/>.
16 */
17 /* The original authors have included a copy of the license specified above in the
18  'Docs' folder. See 'gpl.txt'
19 */
20 /* We welcome the use of the Mezzanine engine to anyone, including companies who wish to
21  Build professional software and charge for their product.
22 
23  However there are some practical restrictions, so if your project involves
24  any of the following you should contact us and we will try to work something
25  out:
26  - DRM or Copy Protection of any kind(except Copyrights)
27  - Software Patents You Do Not Wish to Freely License
28  - Any Kind of Linking to Non-GPL licensed Works
29  - Are Currently In Violation of Another Copyright Holder's GPL License
30  - If You want to change our code and not add a few hundred MB of stuff to
31  your distribution
32 
33  These and other limitations could cause serious legal problems if you ignore
34  them, so it is best to simply contact us or the Free Software Foundation, if
35  you have any questions.
36 
37  Joseph Toppi - toppij@gmail.com
38  John Blackwood - makoenergy02@gmail.com
39 */
40 #ifndef _uimarkupparser_cpp
41 #define _uimarkupparser_cpp
42 
43 #include "UI/markupparser.h"
44 #include "UI/character.h"
45 #include "UI/glyph.h"
46 #include "UI/sprite.h"
47 #include "UI/texttoken.h"
48 
49 #include "unicode.h"
50 #include "exception.h"
51 
52 namespace Mezzanine
53 {
54  namespace UI
55  {
56  ///////////////////////////////////////////////////////////////////////////////
57  // MarkupParser Methods
58 
60  { }
61 
63  { }
64 
66  {
67  TextToken* NewTextToken = new TextToken(Text,TextToken::TT_Text);
68  return NewTextToken;
69  }
70 
72  {
73  // Set up our return
74  TextToken* NewTagToken = NULL;
75  const Integer TextSize = Text.size();
76  // Do some string processing to get some more data
77  UInt32 SlashPos = Text.find_first_of('/');
78  if( SlashPos == 1 /* && TextSize > 3 */ ) {
79  // We now know we have a valid end tag
80  // Get the name
81  String TagName = Text.substr(SlashPos + 1,Text.find_first_of(this->GetMarkupTagEnd()) - SlashPos - 1);
82  NewTagToken = new RangeTagToken(Text,TagName,TextToken::TT_RangeTagEnd);
83  }else if( SlashPos == String::npos ) {
84  // No slashes, so this is a start tag or insert tag
85  // Setup the data we'll be using to parse the relevant data
86  Int32 Position = 0;
87  String SeparatorChars(" =");
88  SeparatorChars.append(1,this->GetMarkupTagEnd());
89 
90  // Get the name
91  Position = Text.find_first_of(SeparatorChars);
92  String TagName = Text.substr(1,Position - 1);
93 
94  // Since we have the tags name, found out if it is a range tag, insert tag, or invalid
95  ConstTagIterator TagIt = this->Tags.find(TagName);
96  if( TagIt != this->Tags.end() ) {
97  // We got a valid tag, but is it a range or insert tag?
98  if( (*TagIt).second->IsRangeTag() ) {
99  NewTagToken = new RangeTagToken(Text,TagName,TextToken::TT_RangeTagStart);
100  }else{
101  NewTagToken = new InsertTagToken(Text,TagName);
102  }
103  }else{
104  // Tag doesn't exist for this parser, mark it as an error
105  NewTagToken = new TextToken(Text,TextToken::TT_Error);
106  }
107 
108  if( NewTagToken->GetTokenType() != TextToken::TT_Error )
109  {
110  TagToken* CastedTag = static_cast<TagToken*>( NewTagToken );
111  NameValuePairMap TagParams;
112  if( Text[Position] == '=' ) {
113  TagParams["Value"] = Text.substr(Position + 1,TextSize - Position - 1);
114  }else if( Text[Position] == ' ' ) {
115  // Get the parameters
116  String ParamEndChars(" ");
117  ParamEndChars.append(1,this->GetMarkupTagEnd());
118  while( Position < TextSize - 1 )
119  {
120  String ParamName, ParamValue;
121 
122  UInt32 SeparatorPos = Text.find_first_of('=');
123  if( SeparatorPos != String::npos ) ParamName = Text.substr(Position + 1,SeparatorPos - Position - 1);
124  else break;
125 
126  UInt32 PairEndPos = Text.find_first_of(ParamEndChars);
127  if( SeparatorPos != String::npos ) ParamValue = Text.substr(SeparatorPos + 1,PairEndPos - SeparatorPos - 1);
128  else break;
129 
130  TagParams[ParamName] = ParamValue;
131  Position = PairEndPos;
132  }
133  }
134  // Populate the generated parameters
135  CastedTag->Params.swap(TagParams);
136  }
137  }else{
138  // If we're here, there is a slash not where it's supposed to be which we won't support, so error it
139  NewTagToken = new TextToken(Text,TextToken::TT_Error);
140  }
141  // Return the result
142  return NewTagToken;
143  }
144 
145  void MarkupParser::GenerateCharactersFromToken(const TextToken* Token, TextLayer* Layer, const CharacterTraits& Traits, CharacterContainer& Characters) const
146  {
147  // Setup our data
148  Int32 Position = 0;
149  Int32 BytesAdvance = 0;
150  const char* StrBuf = Token->Text.data();
151  // Get generat'in
152  while( static_cast<UInt32>(Position) < Token->Text.size() )
153  {
154  Int32 GlyphID = Unicode::GetIntFromCharacter(BytesAdvance,StrBuf + Position);
155  if( GlyphID == -1 )
156  {
157  MEZZ_EXCEPTION(Exception::PARAMETERS_EXCEPTION,"Attempting to parse non-UTF8 encoded markup text. Encode in UTF8 and try again.");
158  }
159 
160  Glyph* TheGlyph = Traits.CharFont->GetGlyph(static_cast<UInt32>(GlyphID));
161  if( TheGlyph == NULL )
162  {
163  StringStream ExceptionStream;
164  ExceptionStream << "Attempting to parse unknown Glyph ID: " << GlyphID << ". Provided font (" << Traits.CharFont->GetName() << ") does not contain that Glyph.";
165  MEZZ_EXCEPTION(Exception::PARAMETERS_EXCEPTION,ExceptionStream.str());
166  }
167  /// @todo As the CharacterTraits class expands, so does this logic.
168  Character* NewChar = new Character(TheGlyph,Layer);
169  NewChar->SetCharacterColour(Traits.CharColour);
170  //NewChar->SetHighlightColour(Traits.HLCharColour);
171  Characters.push_back(NewChar);
172 
173  Position += BytesAdvance;
174  }
175  }
176 
177  void MarkupParser::RegenerateTraits(CharacterTraits& Traits, const TagVector& ActiveTags, TextLayer* Layer) const
178  {
179  for( ConstTagVecIterator TagIt = ActiveTags.begin() ; TagIt != ActiveTags.end() ; ++TagIt )
180  {
181  // Ignore the return
182  (*TagIt).second->Process( (*TagIt).first->Params, Traits, Layer );
183  }
184  }
185 
186  ///////////////////////////////////////////////////////////////////////////////
187  // Parsing Methods
188 
189  MarkupParser::CharacterContainer MarkupParser::Parse(const String& Source, const CharacterTraits& InitialTraits, TextLayer* CallingLayer) const
190  {
191  // Tokenize our string
192  TokenString* Tokens = this->Lex(Source);
193 
194  // Do our processing
195  CharacterContainer GeneratedCharacters = this->ParseTextTokens(Tokens,InitialTraits,CallingLayer);
196 
197  // Cleanup and return
198  delete Tokens;
199  Tokens = NULL;
200 
201  return GeneratedCharacters;
202  }
203 
205  {
206  // Character Data
207  CharacterContainer GeneratedCharacters;
208  CharacterTraits CurrentTraits = InitialTraits;
209 
210  // Setup our container that will cache the active tags for our position
211  TagVector ActiveTags;
212 
213  // Lets process some tokens
214  for( TokenString::TokenIterator TokIt = Tokens->BeginToken() ; TokIt != Tokens->EndToken() ; ++TokIt )
215  {
216  CharacterContainer CharacterSegment;
217  switch( (*TokIt)->Type )
218  {
219  // If this is an error token, just treat it as normal text
220  case TextToken::TT_Text:
221  case TextToken::TT_Error:
223  {
224  // Generate and append
225  TextToken* CurrToken = (*TokIt);
226  this->GenerateCharactersFromToken( (*TokIt),CallingLayer,CurrentTraits,CharacterSegment );
227  GeneratedCharacters.splice(GeneratedCharacters.end(),CharacterSegment);
228  CurrToken->RenderSize = CharacterSegment.size();
229  break;
230  }
232  {
233  RangeTagToken* CurrToken = static_cast<RangeTagToken*>( (*TokIt) );
234  // See if this tag is supported by this implementation
235  ConstTagIterator TagIt = this->Tags.find(CurrToken->GetTagName());
236  if( TagIt != Tags.end() ) {
237  // If it is, verify it applies to a range
238  if( (*TagIt).second->IsRangeTag() ) {
239  // Ensure it has a partner
240  if( CurrToken->PartnerTag != NULL ) {
241  // If it's a range token and has a partner then we should be good
242  MarkupTag::ProcessResult Res = (*TagIt).second->Process(CurrToken->Params,CurrentTraits,CallingLayer);
243  if( Res.first ) {
244  if( Res.second ) {
245  // This shouldn't really ever execute
246  CharacterSegment.push_back(Res.second);
247  }
248 
249  ActiveTags.push_back( TokenTagPair( CurrToken,(*TagIt).second ) );
250  }else{
251  this->GenerateCharactersFromToken( CurrToken,CallingLayer,CurrentTraits,CharacterSegment );
252  }
253  }else{
254  this->GenerateCharactersFromToken( CurrToken,CallingLayer,CurrentTraits,CharacterSegment );
255  }
256  }else{
257  this->GenerateCharactersFromToken( CurrToken,CallingLayer,CurrentTraits,CharacterSegment );
258  }
259  }else{
260  this->GenerateCharactersFromToken( CurrToken,CallingLayer,CurrentTraits,CharacterSegment );
261  }
262 
263  // If anything was generated, append it
264  if( !CharacterSegment.empty() )
265  {
266  GeneratedCharacters.splice(GeneratedCharacters.end(),CharacterSegment);
267  CurrToken->RenderSize = CharacterSegment.size();
268  }
269 
270  break;
271  }
273  {
274  RangeTagToken* CurrToken = static_cast<RangeTagToken*>( (*TokIt) );
275  if( CurrToken->PartnerTag != NULL ) {
276  // If the tag has a partner, then it's time it's effects come to an end
277  for( TagVector::reverse_iterator TagIt = ActiveTags.rbegin() ; TagIt != ActiveTags.rend() ; ++TagIt )
278  {
279  if( (*TagIt).first == CurrToken->PartnerTag )
280  // erase only accepts normal iterators which reverse iterators are offset from, adjust before passing in
281  ActiveTags.erase( --(TagIt.base()) );
282  }
283  // Update our traits based on remaining active tags
284  CurrentTraits = InitialTraits;
285  this->RegenerateTraits(CurrentTraits,ActiveTags,CallingLayer);
286  }else{
287  // End tags always belong to a range, without a partner tag this is just an error
288  this->GenerateCharactersFromToken( CurrToken,CallingLayer,CurrentTraits,CharacterSegment );
289  GeneratedCharacters.splice(GeneratedCharacters.end(),CharacterSegment);
290  CurrToken->RenderSize = CharacterSegment.size();
291  }
292  break;
293  }
295  {
296  InsertTagToken* CurrToken = static_cast<InsertTagToken*>( (*TokIt) );
297  // See if this tag is supported by this implementation
298  ConstTagIterator TagIt = this->Tags.find(CurrToken->GetTagName());
299  if( TagIt != Tags.end() ) {
300  MarkupTag::ProcessResult Res = (*TagIt).second->Process(CurrToken->Params,CurrentTraits,CallingLayer);
301  if( Res.first ) {
302  if( Res.second ) {
303  CharacterSegment.push_back(Res.second);
304  }
305  }else{
306  this->GenerateCharactersFromToken( CurrToken,CallingLayer,CurrentTraits,CharacterSegment );
307  }
308  }else{
309  this->GenerateCharactersFromToken( CurrToken,CallingLayer,CurrentTraits,CharacterSegment );
310  GeneratedCharacters.splice(GeneratedCharacters.end(),CharacterSegment);
311  CurrToken->RenderSize = CharacterSegment.size();
312  }
313  }// case insert tag
314  }// switch token type
315  }// for each token
316  return GeneratedCharacters;
317  }
318 
319  TokenString* MarkupParser::Lex(const String& Source) const
320  {
321  // Return Data
322  TokenString* RetTokens = new TokenString();
323 
324  size_t Position = 0;
325  // Lets process some data
326  while( Position < Source.size() )
327  {
328  // Find our start tag
329  size_t TagStartPos = Source.find_first_of( this->GetMarkupTagStart(), Position );
330 
331  // Deal with non-tag text if needed
332  if( TagStartPos == String::npos )
333  {
334  // No tag for the remainder of the string, make our token and then exit
335  RetTokens->PushToken( CreateTextToken( Source.substr(Position) ) );
336  break;
337  }
338  else if( TagStartPos - Position > 0 )
339  {
340  // We have text between our current position and our tag, make a token for it
341  RetTokens->PushToken( CreateTextToken( Source.substr(Position,TagStartPos - Position) ) );
342  }
343 
344  // If we get this far, we got a tag so find the closing character
345  size_t TagEndPos = Source.find_first_of( this->GetMarkupTagEnd(), TagStartPos );
346 
347  // if we didn't get a match, this isn't a valid token so make a text token instead
348  if( TagEndPos == String::npos )
349  {
350  RetTokens->PushToken( CreateTextToken( Source.substr(TagStartPos) ) );
351  break;
352  }
353 
354  // Otherwise make a proper tag token and link it if it's an end tag
355  TextToken* NewTagToken = CreateTagToken( Source.substr(TagStartPos,TagEndPos - TagStartPos + 1) );
356  if( NewTagToken->Type == TextToken::TT_RangeTagEnd )
357  {
358  RangeTagToken* EndToken = static_cast<RangeTagToken*>( NewTagToken );
359  for( TokenString::ReverseTokenIterator TokIt = RetTokens->ReverseBeginToken() ; TokIt != RetTokens->ReverseEndToken() ; ++TokIt )
360  {
361  if( (*TokIt)->Type == TextToken::TT_RangeTagStart )
362  {
363  RangeTagToken* TagCheck = static_cast<RangeTagToken*>( (*TokIt) );
364  if( TagCheck->PartnerTag == NULL && TagCheck->TagName == EndToken->TagName )
365  {
366  TagCheck->PartnerTag = EndToken;
367  EndToken->PartnerTag = TagCheck;
368  }
369  }
370  }
371  }
372  RetTokens->PushToken( NewTagToken );
373 
374  // Update our position for the next loop
375  Position = TagEndPos + 1;
376  }
377  // Got our tokens, return 'em
378  return RetTokens;
379  }
380  }//UI
381 }//Mezzanine
382 
383 #endif