1/**************************************************************************** 2* 3* wxWindows HTML Applet Package 4* 5* Copyright (C) 1991-2001 SciTech Software, Inc. 6* All rights reserved. 7* 8* ======================================================================== 9* 10* The contents of this file are subject to the wxWindows License 11* Version 3.0 (the "License"); you may not use this file except in 12* compliance with the License. You may obtain a copy of the License at 13* http://www.wxwindows.org/licence3.txt 14* 15* Software distributed under the License is distributed on an 16* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 17* implied. See the License for the specific language governing 18* rights and limitations under the License. 19* 20* ======================================================================== 21* 22* Language: ANSI C++ 23* Environment: Any 24* 25* Description: This file is the implementation of the Preprocessor object 26* for parsing the <!--#if directive 27* 28****************************************************************************/ 29 30// Include private headers 31#include "wx/applet/prepifelse.h" 32#include "wx/applet/ifelsevar.h" 33#include "wx/applet/echovar.h" 34#include "wx/string.h" 35 36// Force link macro 37#include "wx/html/forcelnk.h" 38// wxWindows 39#include "wx/msgdlg.h" 40 41/*----------------------------- Implementation ----------------------------*/ 42 43/* {SECRET} */ 44/**************************************************************************** 45REMARKS: 46None of the Reverse Find functions in wxWindows appear to work in a way that 47can be used by our code. This includes the libstr rfind implementations which 48do not correctly pass the given return value. 49****************************************************************************/ 50int ReverseFind( 51 const wxString &tstr, 52 const wxString &str, 53 int start = -1) 54{ 55 wxASSERT( str.GetStringData()->IsValid() ); 56 57 // TODO could be made much quicker than that 58 int p = tstr.Len()-str.Len()-1; 59 int p2 = start-str.Len(); 60 61 // if the user supplied a valid start point, use it 62 if (start != -1 && p > p2) p = p2; 63 while ( p >= 0 ) { 64 if ( wxStrncmp(tstr.c_str() + p, str.c_str(), str.Len()) == 0 ) 65 return p; 66 p--; 67 } 68 return -1; 69} 70 71/* {SECRET} */ 72/**************************************************************************** 73REMARKS: 74tells if a character is a letter. 75replace this when wxWindows gets regex library. (without strange licensing 76restrictions) 77****************************************************************************/ 78bool IsLetter( 79 char c, bool acceptspace = false) 80{ 81 if (acceptspace && (c == ' ')) return true; 82 if (c >= '0' && c <= '9') return true; 83 return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '\"' || c == '\'' ); 84} 85 86#define IsQuote(c) (c == '\'' || c == '\"') 87 88/* {SECRET} */ 89/**************************************************************************** 90REMARKS: 91tells if a character is a letter. 92replace this when wxWindows gets regex library. (without strange licensing 93restrictions) 94****************************************************************************/ 95wxString GetEquals( 96 wxString var, 97 wxString value) 98{ 99 if (!wxEchoVariable::Exists(var)) { 100 // TODO: when we implement the set variable, check for a set variable as well 101 #ifdef CHECKED 102 wxMessageBox(wxString("wxHTML #if\\else error: Variable ") + var + wxString(" not found."),"Error",wxICON_ERROR); 103 #endif 104 return wxString("0"); // false 105 } 106 107 wxString tmp = wxEchoVariable::GetValue(var); 108 109 if (IsQuote( value.GetChar(0) )) 110 value = value.Mid(1); 111 if (IsQuote(value.GetChar(value.Length()-1))) 112 value = value.Mid(0,value.Length()-1); 113 114 if (tmp.CmpNoCase(value) == 0) return wxString("1"); 115 return wxString("0"); 116} 117 118/**************************************************************************** 119PARAMETERS: 120str - text of #if statement 121 122RETURNS: 123true or false depending on how it evaluated 124 125REMARKS: 126TODO: rewrite this whole thing using regular expressions when they are done. 127 128SEE ALSO: 129wxIfElseVariable 130****************************************************************************/ 131bool ParseIfStatementValue( 132 wxString &str) 133{ 134 // Find out if the tag has parenthesis 135 // recursive to parse the text within the parenthesis, 136 // replacing the text with 1 or 0, (hardcoded true or false) 137 int b; 138 while ((b = str.Find('(')) != -1) { 139 int e; 140 // Find the matching parenthesis 141 int nextbeg, nextend; 142 int parencount = 1, min = b+1; 143 do { 144 nextbeg = str.find('(', min); 145 nextend = str.find(')', min); 146 if (nextbeg < nextend && nextbeg != wxString::npos) { 147 parencount++; 148 min = nextbeg+1; 149 } 150 else { 151 parencount--; 152 min = nextend+1; 153 } 154 155 if (nextend == wxString::npos) { 156#ifdef CHECKED 157 wxMessageBox("wxHTML #if\\else error: Unmatched parenthesis in #if expression.","Error",wxICON_ERROR); 158#endif 159 return true; 160 } 161 // once parencount reaches 0 again we have found our matchin ) 162 } while (parencount > 0); 163 164 e = nextend; 165 166 // Extract the expression from the parenthesis block and recurse 167 // to solve it. 168 wxString tag; 169 tag = str.Mid(b+1, e-b-1); 170 bool val = ParseIfStatementValue(tag); 171 // Add extra spaces just in case of NOT(VAL) 172 if (val) str = str.Mid(0, b) + " 1" + str.Mid(e+1); 173 else str = str.Mid(0, b) + " 0" + str.Mid(e+1); 174 } 175 176 // Remove spaces from left and right 177 str.Trim(false); 178 str.Trim(true); 179 180 // Convert text method of operators "AND" and "OR" to c style 181 // this makes only one special case necessary for each later on 182 str.Replace(" AND ", "&&"); 183 str.Replace(" OR ", "||"); 184 str.Replace(" EQUALS ", "=="); 185 186 // Check for equals statements 187 // == statements are special because they are evaluated as a single block 188 int equ; 189 equ = str.find("=="); 190 while (equ != wxString::npos) { 191 int begin, end; 192 int begin2, end2; // ends of words 193 begin = equ-1; 194 end = equ+2; 195 196 // remove spaces, find extents 197 while (end < str.Length() && str.GetChar(end) == ' ') 198 end++; 199 while (begin >= 0 && str.GetChar(begin) == ' ') 200 begin--; 201 end2 = end; 202 begin2 = begin; 203 if (str.GetChar(end2) == '\'' || str.GetChar(end2) == '\"') { 204 end2++; 205 while (end2 < str.Length() && str.GetChar(end2) != '\'' && str.GetChar(end2) != '\"' ) 206 end2++; 207 end2++; 208 } 209 else { 210 while (end2 < str.Length() && IsLetter(str.GetChar(end2))) 211 end2++; 212 } 213 while (begin >= 0 && IsLetter(str.GetChar(begin))) 214 begin--; 215 216 if (begin < 0) begin = 0; 217 else begin++; 218 if (end2 >= str.Length()) end2 = str.Length(); 219 220 wxString tmpeq = GetEquals(str.Mid(begin, begin2-begin+1), str.Mid(end, end2-end)); 221 str = str.Mid(0, begin) + wxString(" ") + tmpeq + wxString(" ") + 222 str.Mid(end2); 223 equ = str.find("=="); 224 225 // Remove spaces from left and right 226 str.Trim(false); 227 str.Trim(true); 228 } 229 230 // We use ReverseFind so that the whole left expression gets evaluated agains 231 // the right single item, creating a left -> right evaluation 232 // Search for || operators, recurse to solve (so we don't have to handle special cases here) 233 int and, or; 234 and = ReverseFind(str, "&&"); 235 or = ReverseFind(str, "||"); 236 if ( (and != -1) || (or != -1) ) { 237 wxString tag1, tag2; 238 // handle the rightmost first to force left->right evaluation 239 if ( (and > or) ) { 240 return ( 241 ParseIfStatementValue(tag2 = str.Mid(and+2)) && 242 ParseIfStatementValue(tag1 = str.Mid(0, and)) ); 243 } 244 else { 245 return ( 246 ParseIfStatementValue(tag2 = str.Mid(or+2)) || 247 ParseIfStatementValue(tag1 = str.Mid(0, or)) ); 248 } 249 } 250 251 // By the time we get to this place in the function we are guarenteed to have a single 252 // variable operation, perhaps with a NOT or ! operator 253 bool notval = false; 254 255 // search for a NOT or ! operator 256 if (str.Mid(0, 1) == "!") { 257 str.Remove(0, 1); 258 str.Trim(false); // trim spaces from left 259 notval = true; 260 } 261 else if (str.Mid(0,4).CmpNoCase("NOT ") == 0) { 262 str.Remove(0, 4); 263 str.Trim(false); // trim any extra spaces from left 264 notval = true; 265 } 266 267 // now all we have left is the name of the class or a hardcoded 0 or 1 268 if (str == "") { 269#ifdef CHECKED 270 wxMessageBox("wxHTML #if\\else error: Empty expression in #if\\#elif statement.","Error",wxICON_ERROR); 271#endif 272 return true; 273 } 274 275 // check for hardcoded 0 and 1 cases, (these are used by parenthesis catcher) 276 // this just decomplicates the recursion algorithm 277 if (str == "0") return notval; 278 if (str == "1") return !notval; 279 280 // Grab the value from the variable class identified by cname 281 bool value = wxIfElseVariable::GetValue(str); 282 if (notval) value = !value; 283 return value; 284 285} 286/**************************************************************************** 287PARAMETERS: 288text - HTML to process for if/else blocks 289 290RETURNS: 291The string containing the processed HTML 292 293REMARKS: 294This function replaces #if, #else, #elif, and #endif directives with the text 295contained within the blocks, dependant on the value of the given boolean 296variable. The variable is created by making a sub class of wxIfElseVariable. 297Dynamic class construction is used at run time internally to create an instance 298of this class and access the value of the variable. 299 300SEE ALSO: 301wxIfElseVariable 302****************************************************************************/ 303wxString wxIfElsePrep::Process( 304 const wxString& text) const 305{ 306 int b; 307 char ft[] = "<!--#if "; 308 char ftend[] = "<!--#endif-->"; 309 char ftelse[] = "<!--#else-->"; 310 char ftnot[] = "<!--#if not "; 311 char ftnot2[] = "<!--#if !"; 312 char ftelif[] = "<!--#elif "; 313 314 // make a copy so we can replace text as we go without affecting the original 315 wxString output = text; 316 317 // Avoid duplication of our parsing code by turning any #elif blocks into appropriate 318 // else/if blocks 319 while ((b = ReverseFind(output.Lower(), ftelif)) != -1) { 320 int e; 321 // Replace beginning of block 322 e = output.find("-->", b + strlen(ftelif)); 323 324 if (e == wxString::npos) { 325#ifdef CHECKED 326 wxMessageBox("wxHTML #elif error: Premature end of file while parsing #elif.","Error",wxICON_ERROR); 327#endif 328 break; 329 } 330 331 // Convert to lower case so find is easy, grab everything after #elif tag 332 wxString remains = (output.Mid(e+strlen("-->"))).Lower(); 333 334 // find matching else or endif 335 int nextif, nextendif; 336 int ifcount = 1, min = 0; 337 do { 338 nextif = remains.find(ft, min); 339 nextendif = remains.find(ftend, min); 340 if (nextif < nextendif && nextif != wxString::npos) { 341 ifcount++; 342 min = nextif+1; 343 } 344 else { 345 ifcount--; 346 min = nextendif+1; 347 } 348 349 if (nextendif == wxString::npos) { 350#ifdef CHECKED 351 wxMessageBox("wxHTML #elif error: Premature end of file before finding #endif.","Error",wxICON_ERROR); 352#endif 353 break; 354 } 355 // once ifcount reaches 0 again we have found our matchin #endif 356 } while (ifcount > 0); 357 358 // If it couldn't be found die gracefully 359 if (nextendif == wxString::npos) { 360 // We already displayed a message, just break all the way out 361 break; 362 } 363 364 int elifsize = e - (b + strlen(ftelif)) + strlen("-->"); 365 // Create the #if/else block, removing the #elif code 366 output = output.Mid(0, b) + 367 wxString(wxString(ftelse)+wxString(ft)) + 368 output.Mid(b+strlen(ftelif), elifsize+nextendif) + 369 wxString(ftend) + 370 output.Mid(b+strlen(ftelif)+elifsize+nextendif); 371 } 372 373 // Parse out the if else blocks themselves 374 while ((b = ReverseFind(output.Lower(), ft)) != -1) { 375 // Loop until every #if directive is found 376 // We search from the end of the string so that #if statements will properly recurse 377 // and we avoid the hassle of matching statements with the correct <!--#endif--> 378 bool notval = false; 379 int off = 0; 380 int end; 381 wxString usecode, code; 382 wxString cname; 383 wxString tag; 384 bool value; 385 386 code = wxString(""); 387 388 // grab the tag and get the name of the variable 389 end = (output.Mid(b)).Find("-->"); 390 if (end == -1) { 391#ifdef CHECKED 392 wxMessageBox("wxHTML #if error: Premature end of file while parsing #if.","Error",wxICON_ERROR); 393#endif 394 break; 395 } 396 397 end += 3; 398 // remove the <!--#if and --> sections from the tag before passing it on to be parsed 399 tag = output.Mid(b+strlen(ft), end-strlen(ft)-3); 400 output.Remove(b, end); 401 402 value = ParseIfStatementValue(tag); 403 404 // Find the end of the tag (<!--#endif-->) and copy it all into the variable code 405 end = ((output.Mid(b)).Lower()).Find(ftend); 406 if (end == -1) { 407#ifdef CHECKED 408 wxMessageBox("wxHTML #if error: Premature end of file while searching for matching #endif.","Error",wxICON_ERROR); 409#endif 410 break; 411 } 412 413 code = output.Mid(b, end); 414 output.Remove(b, end+strlen(ftend)); // remove the entire #if block from original document 415 416 // Find out if there is an else statement 417 end = (code.Lower()).Find(ftelse); 418 if (end != -1) { 419 if (!value) { 420 // Use the else statement 421 usecode = code.Mid(end+strlen(ftelse)); 422 } 423 else { 424 // Use statement before #else 425 usecode = code.Mid(0, end); 426 } 427 } 428 else if (value) { 429 // There is no #else statement 430 usecode = code; 431 } 432 433 if (usecode != wxString("")) 434 output = (output.Mid(0,b) + usecode + output.Mid(b)); 435 } 436 437 return output; 438} 439 440FORCE_LINK(ifelsevar) 441 442