1import re 2import pywmlx.state.machine 3from pywmlx.state.state import State 4import pywmlx.nodemanip 5from pywmlx.wmlerr import wmlerr 6 7 8 9class WmlIdleState: 10 def __init__(self): 11 self.regex = None 12 self.iffail = None 13 14 def run(self, xline, lineno, match): 15 _nextstate = 'wml_checkdom' 16 if pywmlx.state.machine._pending_wmlstring is not None: 17 pywmlx.state.machine._pending_wmlstring.store() 18 pywmlx.state.machine._pending_wmlstring = None 19 m = re.match(r'\s*$', xline) 20 if m: 21 xline = None 22 _nextstate = 'wml_idle' 23 return (xline, _nextstate) # 'wml_define' 24 25 26 27''' 28class WmlDefineState: 29 def __init__(self): 30 self.regex = re.compile('\s*#(define|enddef|\s+wmlxgettext:\s+)', re.I) 31 self.iffail = 'wml_checkdom' 32 33 def run(self, xline, lineno, match): 34 if match.group(1).lower() == 'define': 35 # define 36 xline = None 37 if pywmlx.nodemanip.onDefineMacro is False: 38 pywmlx.nodemanip.onDefineMacro = True 39 else: 40 err_message = ("expected an #enddef before opening ANOTHER " + 41 "macro definition with #define") 42 finfo = pywmlx.nodemanip.fileref + ":" + str(lineno) 43 wmlerr(finfo, err_message) 44 elif match.group(1).lower() == 'enddef': 45 # enddef 46 xline = None 47 if pywmlx.nodemanip.onDefineMacro is True: 48 pywmlx.nodemanip.onDefineMacro = False 49 else: 50 err_message = ("found an #enddef, but no macro definition " + 51 "is pending. Perhaps you forgot to put a " + 52 "#define somewhere?") 53 finfo = pywmlx.nodemanip.fileref + ":" + str(lineno) 54 wmlerr(finfo, err_message) 55 else: 56 # wmlxgettext: {WML CODE} 57 xline = xline [ match.end(): ] 58 return (xline, 'wml_idle') 59''' 60 61 62 63class WmlCheckdomState: 64 def __init__(self): 65 self.regex = re.compile(r'\s*#textdomain\s+(\S+)', re.I) 66 self.iffail = 'wml_checkpo' 67 68 def run(self, xline, lineno, match): 69 pywmlx.state.machine._currentdomain = match.group(1) 70 xline = None 71 return (xline, 'wml_idle') 72 73 74 75class WmlCheckpoState: 76 def __init__(self): 77 rx = r'\s*#\s*(wmlxgettext|po-override|po):\s+(.+)' 78 self.regex = re.compile(rx, re.I) 79 self.iffail = 'wml_comment' 80 81 def run(self, xline, lineno, match): 82 if match.group(1) == 'wmlxgettext': 83 xline = match.group(2) 84 # on #po: addedinfo 85 elif match.group(1) == "po": 86 xline = None 87 if pywmlx.state.machine._pending_addedinfo is None: 88 pywmlx.state.machine._pending_addedinfo = [ match.group(2) ] 89 else: 90 pywmlx.state.machine._pending_addedinfo.append(match.group(2)) 91 # on -- #po-override: overrideinfo 92 elif pywmlx.state.machine._pending_overrideinfo is None: 93 pywmlx.state.machine._pending_overrideinfo = [ match.group(2) ] 94 xline = None 95 else: 96 pywmlx.state.machine._pending_overrideinfo.append(match.group(2)) 97 xline = None 98 return (xline, 'wml_idle') 99 100 101 102class WmlCommentState: 103 def __init__(self): 104 self.regex = re.compile(r'\s*#.+') 105 self.iffail = 'wml_str02' 106 107 def run(self, xline, lineno, match): 108 xline = None 109 return (xline, 'wml_idle') 110 111 112 113# On WML you can also have _ << translatable string >>, even if quite rare 114# This is considered here as "WML string 02" since it is a rare case. 115# However, for code safety, it is evaluated here before evaluating tags, and 116# before string 1. Unlike all other strings, this will be evaluated ONLY if 117# translatable, to avoid possible conflicts with WmlGoLuaState (and to prevent 118# to make that state unreachable). 119# In order to ensure that the order of sentences will be respected, the regexp 120# does not match if " is found before _ << 121# In this way the WmlStr01 state (wich is placed after) can be reached and the 122# sentence will not be lost. 123# WARNING: This also means that it is impossible to capture any wmlinfo which 124# uses this kind of translatable string 125# example: name = _ <<Name>> 126# in that case the string "name" will be captured, but the wmlinfo 127# name = Name will be NOT added to automatic informations. 128# This solution is necessary, since extending the workaround 129# done for _ "standard translatable strings" to _ << wmlstr02 >> 130# can introduce serious bugs 131class WmlStr02: 132 def __init__(self): 133 rx = r'[^"]*_\s*<<(?:(.*?)>>|(.*))' 134 self.regex = re.compile(rx) 135 self.iffail = 'wml_tag' 136 137 def run(self, xline, lineno, match): 138 # if will ever happen 'wmlstr02 assertion error' than you could 139 # turn 'mydebug' to 'True' to inspect what exactly happened. 140 # However this should be never necessary 141 mydebug = False 142 _nextstate = 'wml_idle' 143 loc_translatable = True 144 loc_multiline = False 145 loc_string = None 146 # match group(1) exists, so it is a single line string 147 # (group(2) will not exist, than) 148 if match.group(1) is not None: 149 loc_multiline = False 150 loc_string = match.group(1) 151 xline = xline [ match.end(): ] 152 # match.group(2) exists, so it is a multi-line string 153 # (group(1) will not exist, than) 154 elif match.group(2) is not None: 155 loc_multiline = True 156 loc_string = match.group(2) 157 _nextstate = 'wml_str20' 158 xline = None 159 else: 160 if mydebug: 161 err_message = ("wmlstr02 assertion error (DEBUGGING)\n" + 162 'g1: ' + str(match.group(1)) + '\n' + 163 'g2: ' + str(match.group(2)) ) 164 finfo = pywmlx.nodemanip.fileref + ":" + str(lineno) 165 wmlerr(finfo, err_message) 166 else: 167 wmlerr('wmlxgettext python sources', 168 'wmlstr02 assertion error\n' 169 'please report a bug if you encounter this error message') 170 pywmlx.state.machine._pending_wmlstring = ( 171 pywmlx.state.machine.PendingWmlString( 172 lineno, loc_string, loc_multiline, loc_translatable, israw=True 173 ) 174 ) 175 return (xline, _nextstate) 176 177 178 179class WmlTagState: 180 def __init__(self): 181 # this regexp is discussed in depth in Source Documentation, chapter 6 182 rx = r'\s*(?:[^"]+\(\s*)?\[\s*([\/+-]?)\s*([A-Za-z0-9_]+)\s*\]' 183 self.regex = re.compile(rx) 184 self.iffail = 'wml_getinf' 185 186 def run(self, xline, lineno, match): 187 # xdebug = open('./debug.txt', 'a') 188 # xdebug_str = None 189 if match.group(1) == '/': 190 closetag = '[/' + match.group(2) + ']' 191 pywmlx.nodemanip.closenode(closetag, 192 pywmlx.state.machine._dictionary, 193 lineno) 194 if closetag == '[/lua]': 195 pywmlx.state.machine._pending_luafuncname = None 196 pywmlx.state.machine._on_luatag = False 197 # xdebug_str = closetag + ': ' + str(lineno) 198 else: 199 opentag = '[' + match.group(2) + ']' 200 pywmlx.nodemanip.newnode(opentag) 201 if(opentag == '[lua]'): 202 pywmlx.state.machine._on_luatag = True 203 # xdebug_str = opentag + ': ' + str(lineno) 204 # print(xdebug_str, file=xdebug) 205 # xdebug.close() 206 pywmlx.state.machine._pending_addedinfo = None 207 pywmlx.state.machine._pending_overrideinfo = None 208 xline = xline [ match.end(): ] 209 return (xline, 'wml_idle') 210 211 212 213class WmlGetinfState: 214 def __init__(self): 215 rx = ( r'\s*(speaker|id|role|description|condition|type|race)' + 216 r'\s*=\s*(.*)' ) 217 self.regex = re.compile(rx, re.I) 218 self.iffail = 'wml_str01' 219 def run(self, xline, lineno, match): 220 _nextstate = 'wml_idle' 221 if '"' in match.group(2): 222 _nextstate = 'wml_str01' 223 pywmlx.state.machine._pending_winfotype = match.group(1) 224 else: 225 loc_wmlinfo = match.group(1) + '=' + match.group(2) 226 xline = None 227 pywmlx.nodemanip.addWmlInfo(loc_wmlinfo) 228 return (xline, _nextstate) 229 230 231 232class WmlStr01: 233 def __init__(self): 234 rx = r'(?:[^"]*?)\s*(_?)\s*"((?:""|[^"])*)("?)' 235 self.regex = re.compile(rx) 236 self.iffail = 'wml_golua' 237 238 def run(self, xline, lineno, match): 239 _nextstate = 'wml_idle' 240 loc_translatable = True 241 if match.group(1) == "": 242 loc_translatable = False 243 loc_multiline = False 244 if match.group(3) == "": 245 xline = None 246 loc_multiline = True 247 _nextstate = 'wml_str10' 248 else: 249 xline = xline [ match.end(): ] 250 pywmlx.state.machine._pending_wmlstring = ( 251 pywmlx.state.machine.PendingWmlString( 252 lineno, match.group(2), loc_multiline, loc_translatable, israw=False 253 ) 254 ) 255 return (xline, _nextstate) 256 257 258 259# well... the regex will always be true on this state, so iffail will never 260# be executed 261class WmlStr10: 262 def __init__(self): 263 self.regex = re.compile(r'((?:""|[^"])*)("?)') 264 self.iffail = 'wml_str10' 265 266 def run(self, xline, lineno, match): 267 _nextstate = None 268 pywmlx.state.machine._pending_wmlstring.addline( match.group(1) ) 269 if match.group(2) == "": 270 _nextstate = 'wml_str10' 271 xline = None 272 else: 273 _nextstate = 'wml_idle' 274 xline = xline [ match.end(): ] 275 return (xline, _nextstate) 276 277 278 279class WmlStr20: 280 def __init__(self): 281 self.regex = None 282 self.iffail = None 283 284 def run(self, xline, lineno, match): 285 realmatch = re.match(r'(.*?)>>', xline) 286 _nextstate = 'wml_str20' 287 if realmatch: 288 pywmlx.state.machine._pending_wmlstring.addline( 289 realmatch.group(1) ) 290 xline = xline [ realmatch.end(): ] 291 _nextstate = 'wml_idle' 292 else: 293 pywmlx.state.machine._pending_wmlstring.addline(xline) 294 xline = None 295 _nextstate = 'wml_str20' 296 return (xline, _nextstate) 297 298 299 300# Only if the symbol '<<' is found inside a [lua] tag, then it means we are 301# actually starting a lua code. 302# It can happen that WML uses the '<<' symbol in a very different context 303# wich has nothing to do with lua, so switching to the lua states in that 304# case can lead to problems. 305# This happened on the file data/gui/default/widget/toggle_button_orb.cfg 306# on wesnoth 1.13.x, where there is this line inside the first [image] tag: 307# 308# name = "('buttons/misc/orb{STATE}.png" + <<~RC(magenta>{icon})')>> 309# 310# In that case, after 'name' there is a WML string 311# "('buttons/misc/orb{STATE}.png" 312# And after that you find a concatenation with a literal string 313# <<~RC(magenta>{icon})')>> 314# 315# That second string has nothing to do with lua, and, most importantly, if 316# it is parsed with lua states, it returns an error... why? 317# Simply because of the final ' symbol, wich is a valid symbol, in lua, for 318# opening a new string; but, in that case, there is not an opening string, 319# but a ' symbol that must be used literally. 320# 321# This is why we use a global var _on_luatag in state.py wich is usually False. 322# it will be set to True only when opening a lua tag (see WmlTagState) 323# it will be set to False again when the lua tag is closed (see WmlTagState) 324class WmlGoluaState: 325 def __init__(self): 326 self.regex = re.compile(r'.*?<<\s*') 327 self.iffail = 'wml_final' 328 329 def run(self, xline, lineno, match): 330 if pywmlx.state.machine._on_luatag: 331 xline = xline [ match.end(): ] 332 return (xline, 'lua_idle') 333 else: 334 return (xline, 'wml_final') 335 336 337 338class WmlFinalState: 339 def __init__(self): 340 self.regex = None 341 self.iffail = None 342 343 def run(self, xline, lineno, match): 344 xline = None 345 if pywmlx.state.machine._pending_wmlstring is not None: 346 pywmlx.state.machine._pending_wmlstring.store() 347 pywmlx.state.machine._pending_wmlstring = None 348 return (xline, 'wml_idle') 349 350 351 352def setup_wmlstates(): 353 for statename, stateclass in [ ('wml_idle', WmlIdleState), 354 # ('wml_define', WmlDefineState), 355 ('wml_checkdom', WmlCheckdomState), 356 ('wml_checkpo', WmlCheckpoState), 357 ('wml_comment', WmlCommentState), 358 ('wml_str02', WmlStr02), 359 ('wml_tag', WmlTagState), 360 ('wml_getinf', WmlGetinfState), 361 ('wml_str01', WmlStr01), 362 ('wml_str10', WmlStr10), 363 ('wml_str20', WmlStr20), 364 ('wml_golua', WmlGoluaState), 365 ('wml_final', WmlFinalState)]: 366 st = stateclass() 367 pywmlx.state.machine.addstate(statename, 368 State(st.regex, st.run, st.iffail) ) 369