comparison orpg/chat/chatwnd.py @ 71:449a8900f9ac ornery-dev

Code refining almost completed, for this round. Some included files are still in need of some clean up, but this is test worthy.
author sirebral
date Thu, 20 Aug 2009 03:00:39 -0500
parents 8fb07d0a1ca0
children dd4be4817377
comparison
equal deleted inserted replaced
70:52a5fa913008 71:449a8900f9ac
47 from orpg.player_list import WG_LIST 47 from orpg.player_list import WG_LIST
48 from orpg.dirpath import dir_struct 48 from orpg.dirpath import dir_struct
49 import orpg.tools.rgbhex 49 import orpg.tools.rgbhex
50 import orpg.tools.inputValidator 50 import orpg.tools.inputValidator
51 #from orpg.tools.metamenus import MenuEx #Needed? 51 #from orpg.tools.metamenus import MenuEx #Needed?
52 from orpg.orpgCore import component 52
53 import webbrowser 53 import webbrowser
54 from string import * 54 from string import *
55 from orpg.orpg_version import VERSION 55 from orpg.orpg_version import VERSION
56 import commands 56 import commands
57 import chat_msg 57 import chat_msg
64 import sys 64 import sys
65 import cStringIO # for reading inline imagedata as a stream 65 import cStringIO # for reading inline imagedata as a stream
66 from HTMLParser import HTMLParser 66 from HTMLParser import HTMLParser
67 import chat_util 67 import chat_util
68 import traceback 68 import traceback
69
70 from orpg.tools.validate import validate
71 from orpg.tools.orpg_settings import settings
72 from orpg.orpgCore import component
69 from orpg.tools.orpg_log import logger 73 from orpg.tools.orpg_log import logger
70 from orpg.tools.decorators import debugging 74 from orpg.tools.decorators import debugging
71 NEWCHAT = False 75 NEWCHAT = False
72 try: 76 try:
73 import wx.webview 77 import wx.webview
77 81
78 # Global parser for stripping HTML tags: 82 # Global parser for stripping HTML tags:
79 # The 'tag stripping' is implicit, because this parser echoes every 83 # The 'tag stripping' is implicit, because this parser echoes every
80 # type of html data *except* the tags. 84 # type of html data *except* the tags.
81 class HTMLStripper(HTMLParser): 85 class HTMLStripper(HTMLParser):
86 @debugging
82 def __init__(self): 87 def __init__(self):
83 self.accum = "" 88 self.accum = ""
84 self.special_tags = ['hr', 'br', 'img'] 89 self.special_tags = ['hr', 'br', 'img']
90 @debugging
85 def handle_data(self, data): # quote cdata literally 91 def handle_data(self, data): # quote cdata literally
86 self.accum += data 92 self.accum += data
93 @debugging
87 def handle_entityref(self, name): # entities must be preserved exactly 94 def handle_entityref(self, name): # entities must be preserved exactly
88 self.accum += "&" + name + ";" 95 self.accum += "&" + name + ";"
96 @debugging
89 def handle_starttag(self, tag, attrs): 97 def handle_starttag(self, tag, attrs):
90 if tag in self.special_tags: 98 if tag in self.special_tags:
91 self.accum += '<' + tag 99 self.accum += '<' + tag
92 for attrib in attrs: self.accum += ' ' + attrib[0] + '="' + attrib[1] + '"' 100 for attrib in attrs: self.accum += ' ' + attrib[0] + '="' + attrib[1] + '"'
93 self.accum += '>' 101 self.accum += '>'
102 @debugging
94 def handle_charref(self, name): # charrefs too 103 def handle_charref(self, name): # charrefs too
95 self.accum += "&#" + name + ";" 104 self.accum += "&#" + name + ";"
96 htmlstripper = HTMLStripper() 105 htmlstripper = HTMLStripper()
97 106
98 # utility function; see Post(). 107 # utility function; see Post().
108 @debugging
99 def strip_html(string): 109 def strip_html(string):
100 "Return string tripped of html tags." 110 "Return string tripped of html tags."
101 htmlstripper.reset() 111 htmlstripper.reset()
102 htmlstripper.accum = "" 112 htmlstripper.accum = ""
103 htmlstripper.feed(string) 113 htmlstripper.feed(string)
104 htmlstripper.close() 114 htmlstripper.close()
105 return htmlstripper.accum 115 return htmlstripper.accum
106 116
117 @debugging
107 def log( settings, c, text ): 118 def log( settings, c, text ):
108 filename = settings.get_setting('GameLogPrefix') 119 filename = settings.get_setting('GameLogPrefix')
109 if filename > '' and filename[0] != commands.ANTI_LOG_CHAR: 120 if filename > '' and filename[0] != commands.ANTI_LOG_CHAR:
110 filename = filename + time.strftime( '-%Y-%m-%d.html', time.localtime( time.time() ) ) 121 filename = filename + time.strftime( '-%Y-%m-%d.html', time.localtime( time.time() ) )
111 #filename = time.strftime( filename, time.localtime( time.time() ) ) 122 #filename = time.strftime( filename, time.localtime( time.time() ) )
133 # initialization subroutine 144 # initialization subroutine
134 # 145 #
135 # !self : instance of self 146 # !self : instance of self
136 # !parent : 147 # !parent :
137 # !id : 148 # !id :
149 @debugging
138 def __init__(self, parent, id): 150 def __init__(self, parent, id):
139 wx.html.HtmlWindow.__init__(self, parent, id, style=wx.SUNKEN_BORDER | wx.html.HW_SCROLLBAR_AUTO|wx.NO_FULL_REPAINT_ON_RESIZE) 151 wx.html.HtmlWindow.__init__(self, parent, id, style=wx.SUNKEN_BORDER | wx.html.HW_SCROLLBAR_AUTO|wx.NO_FULL_REPAINT_ON_RESIZE)
140 self.parent = parent 152 self.parent = parent
141 self.build_menu() 153 self.build_menu()
142 self.Bind(wx.EVT_LEFT_UP, self.LeftUp) 154 self.Bind(wx.EVT_LEFT_UP, self.LeftUp)
143 self.Bind(wx.EVT_RIGHT_DOWN, self.onPopup) 155 self.Bind(wx.EVT_RIGHT_DOWN, self.onPopup)
144 if "gtk2" in wx.PlatformInfo: self.SetStandardFonts() 156 if "gtk2" in wx.PlatformInfo: self.SetStandardFonts()
145 # def __init__ - end 157 # def __init__ - end
146 158
159 @debugging
147 def onPopup(self, evt): 160 def onPopup(self, evt):
148 self.PopupMenu(self.menu) 161 self.PopupMenu(self.menu)
149 162
163 @debugging
150 def LeftUp(self, event): 164 def LeftUp(self, event):
151 event.Skip() 165 event.Skip()
152 wx.CallAfter(self.parent.set_chat_text_focus, None) 166 wx.CallAfter(self.parent.set_chat_text_focus, None)
153 167
168 @debugging
154 def build_menu(self): 169 def build_menu(self):
155 self.menu = wx.Menu() 170 self.menu = wx.Menu()
156 item = wx.MenuItem(self.menu, wx.ID_ANY, "Copy", "Copy") 171 item = wx.MenuItem(self.menu, wx.ID_ANY, "Copy", "Copy")
157 self.Bind(wx.EVT_MENU, self.OnM_EditCopy, item) 172 self.Bind(wx.EVT_MENU, self.OnM_EditCopy, item)
158 self.menu.AppendItem(item) 173 self.menu.AppendItem(item)
159 174
175 @debugging
160 def OnM_EditCopy(self, evt): 176 def OnM_EditCopy(self, evt):
161 wx.TheClipboard.Open() 177 wx.TheClipboard.Open()
162 wx.TheClipboard.Clear() 178 wx.TheClipboard.Clear()
163 wx.TheClipboard.SetData(wx.TextDataObject(self.SelectionToText())) 179 wx.TheClipboard.SetData(wx.TextDataObject(self.SelectionToText()))
164 wx.TheClipboard.Close() 180 wx.TheClipboard.Close()
165 181
182 @debugging
166 def scroll_down(self): 183 def scroll_down(self):
167 maxrange = self.GetScrollRange(wx.VERTICAL) 184 maxrange = self.GetScrollRange(wx.VERTICAL)
168 pagesize = self.GetScrollPageSize(wx.VERTICAL) 185 pagesize = self.GetScrollPageSize(wx.VERTICAL)
169 self.Scroll(-1, maxrange-pagesize) 186 self.Scroll(-1, maxrange-pagesize)
170 187
188 @debugging
171 def mouse_wheel(self, event): 189 def mouse_wheel(self, event):
172 amt = event.GetWheelRotation() 190 amt = event.GetWheelRotation()
173 units = amt/(-(event.GetWheelDelta())) 191 units = amt/(-(event.GetWheelDelta()))
174 self.ScrollLines(units*3) 192 self.ScrollLines(units*3)
175 193
194 @debugging
176 def Header(self): 195 def Header(self):
177 return '<html><body bgcolor="' + self.parent.bgcolor + '" text="' + self.parent.textcolor + '">' 196 return '<html><body bgcolor="' + self.parent.bgcolor + '" text="' + self.parent.textcolor + '">'
178 197
198 @debugging
179 def StripHeader(self): 199 def StripHeader(self):
180 return self.GetPageSource().replace(self.Header(), '') 200 return self.GetPageSource().replace(self.Header(), '')
181 201
202 @debugging
182 def GetPageSource(self): 203 def GetPageSource(self):
183 return self.GetParser().GetSource() 204 return self.GetParser().GetSource()
184 205
185 # This subroutine fires up the webbrowser when a link is clicked. 206 # This subroutine fires up the webbrowser when a link is clicked.
186 # 207 #
187 # !self : instance of self 208 # !self : instance of self
188 # !linkinfo : instance of a class that contains the link information 209 # !linkinfo : instance of a class that contains the link information
210 @debugging
189 def OnLinkClicked(self, linkinfo): 211 def OnLinkClicked(self, linkinfo):
190 href = linkinfo.GetHref() 212 href = linkinfo.GetHref()
191 wb = webbrowser.get() 213 wb = webbrowser.get()
192 wb.open(href) 214 wb.open(href)
193 # def OnLinkClicked - end 215 # def OnLinkClicked - end
194 216
217 @debugging
195 def CalculateAllFonts(self, defaultsize): 218 def CalculateAllFonts(self, defaultsize):
196 return [int(defaultsize * 0.4), 219 return [int(defaultsize * 0.4),
197 int(defaultsize * 0.7), 220 int(defaultsize * 0.7),
198 int(defaultsize), 221 int(defaultsize),
199 int(defaultsize * 1.3), 222 int(defaultsize * 1.3),
200 int(defaultsize * 1.7), 223 int(defaultsize * 1.7),
201 int(defaultsize * 2), 224 int(defaultsize * 2),
202 int(defaultsize * 2.5)] 225 int(defaultsize * 2.5)]
203 226
227 @debugging
204 def SetDefaultFontAndSize(self, fontname, fontsize): 228 def SetDefaultFontAndSize(self, fontname, fontsize):
205 """Set 'fontname' to the default chat font. 229 """Set 'fontname' to the default chat font.
206 Returns current font settings in a (fontname, fontsize) tuple.""" 230 Returns current font settings in a (fontname, fontsize) tuple."""
207 self.SetFonts(fontname, "", self.CalculateAllFonts(int(fontsize))) 231 self.SetFonts(fontname, "", self.CalculateAllFonts(int(fontsize)))
208 return (self.GetFont().GetFaceName(), self.GetFont().GetPointSize()) 232 return (self.GetFont().GetFaceName(), self.GetFont().GetPointSize())
209 233
210 # class chat_html_window - end 234 # class chat_html_window - end
211 if NEWCHAT: 235 if NEWCHAT:
212 class ChatHtmlWindow(wx.webview.WebView): 236 class ChatHtmlWindow(wx.webview.WebView):
237 @debugging
213 def __init__(self, parent, id): 238 def __init__(self, parent, id):
214 wx.webview.WebView.__init__(self, parent, id) 239 wx.webview.WebView.__init__(self, parent, id)
215 240
216 self.parent = parent 241 self.parent = parent
217 self.__font = wx.Font(10, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, faceName='Ariel') 242 self.__font = wx.Font(10, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, faceName='Ariel')
220 self.Bind(wx.EVT_LEFT_UP, self.LeftUp) 245 self.Bind(wx.EVT_LEFT_UP, self.LeftUp)
221 self.Bind(wx.EVT_RIGHT_DOWN, self.onPopup) 246 self.Bind(wx.EVT_RIGHT_DOWN, self.onPopup)
222 self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.OnLinkClicked) 247 self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.OnLinkClicked)
223 248
224 #Wrapers so I dont have to add special Code 249 #Wrapers so I dont have to add special Code
250 @debugging
225 def SetPage(self, htmlstring): 251 def SetPage(self, htmlstring):
226 self.SetPageSource(htmlstring) 252 self.SetPageSource(htmlstring)
227 253
254 @debugging
228 def AppendToPage(self, htmlstring): 255 def AppendToPage(self, htmlstring):
229 self.SetPageSource(self.GetPageSource() + htmlstring) 256 self.SetPageSource(self.GetPageSource() + htmlstring)
230 257
258 @debugging
231 def GetFont(self): 259 def GetFont(self):
232 return self.__font 260 return self.__font
233 261
262 @debugging
234 def CalculateAllFonts(self, defaultsize): 263 def CalculateAllFonts(self, defaultsize):
235 return 264 return
236 265
266 @debugging
237 def SetDefaultFontAndSize(self, fontname, fontsize): 267 def SetDefaultFontAndSize(self, fontname, fontsize):
238 self.__font = wx.Font(int(fontsize), wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, faceName=fontname) 268 self.__font = wx.Font(int(fontsize), wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, faceName=fontname)
239 try: self.SetPageSource(self.Header() + self.StripHeader()) 269 try: self.SetPageSource(self.Header() + self.StripHeader())
240 except Exception, e: print e 270 except Exception, e: print e
241 return (self.GetFont().GetFaceName(), self.GetFont().GetPointSize()) 271 return (self.GetFont().GetFaceName(), self.GetFont().GetPointSize())
242 272
243 #Events 273 #Events
274 @debugging
244 def OnLinkClicked(self, linkinfo): 275 def OnLinkClicked(self, linkinfo):
245 href = linkinfo.GetHref() 276 href = linkinfo.GetHref()
246 wb = webbrowser.get() 277 wb = webbrowser.get()
247 wb.open(href) 278 wb.open(href)
248 279
280 @debugging
249 def onPopup(self, evt): 281 def onPopup(self, evt):
250 self.PopupMenu(self.menu) 282 self.PopupMenu(self.menu)
251 283
284 @debugging
252 def LeftUp(self, event): 285 def LeftUp(self, event):
253 event.Skip() 286 event.Skip()
254 wx.CallAfter(self.parent.set_chat_text_focus, None) 287 wx.CallAfter(self.parent.set_chat_text_focus, None)
255 288
289 @debugging
256 def OnM_EditCopy(self, evt): 290 def OnM_EditCopy(self, evt):
257 self.Copy() 291 self.Copy()
258 292
259 #Cutom Methods 293 #Cutom Methods
294 @debugging
260 def Header(self): 295 def Header(self):
261 return "<html><head><style>body {font-size: " + str(self.GetFont().GetPointSize()) + "px;font-family: " + self.GetFont().GetFaceName() + ";color: " + self.parent.textcolor + ";background-color: " + self.parent.bgcolor + ";margin: 0;padding: 0 0;height: 100%;}</style></head><body>" 296 return "<html><head><style>body {font-size: " + str(self.GetFont().GetPointSize()) + "px;font-family: " + self.GetFont().GetFaceName() + ";color: " + self.parent.textcolor + ";background-color: " + self.parent.bgcolor + ";margin: 0;padding: 0 0;height: 100%;}</style></head><body>"
262 297
298 @debugging
263 def StripHeader(self): 299 def StripHeader(self):
264 tmp = self.GetPageSource().split('<BODY>') 300 tmp = self.GetPageSource().split('<BODY>')
265 if tmp[-1].find('<body>') > -1: tmp = tmp[-1].split('<body>') 301 if tmp[-1].find('<body>') > -1: tmp = tmp[-1].split('<body>')
266 return tmp[-1] 302 return tmp[-1]
267 303
304 @debugging
268 def build_menu(self): 305 def build_menu(self):
269 self.menu = wx.Menu() 306 self.menu = wx.Menu()
270 item = wx.MenuItem(self.menu, wx.ID_ANY, "Copy", "Copy") 307 item = wx.MenuItem(self.menu, wx.ID_ANY, "Copy", "Copy")
271 self.Bind(wx.EVT_MENU, self.OnM_EditCopy, item) 308 self.Bind(wx.EVT_MENU, self.OnM_EditCopy, item)
272 self.menu.AppendItem(item) 309 self.menu.AppendItem(item)
273 310
311 @debugging
274 def scroll_down(self): 312 def scroll_down(self):
275 maxrange = self.GetScrollRange(wx.VERTICAL) 313 maxrange = self.GetScrollRange(wx.VERTICAL)
276 pagesize = self.GetScrollPageSize(wx.VERTICAL) 314 pagesize = self.GetScrollPageSize(wx.VERTICAL)
277 self.Scroll(-1, maxrange-pagesize) 315 self.Scroll(-1, maxrange-pagesize)
278 316
317 @debugging
279 def mouse_wheel(self, event): 318 def mouse_wheel(self, event):
280 amt = event.GetWheelRotation() 319 amt = event.GetWheelRotation()
281 units = amt/(-(event.GetWheelDelta())) 320 units = amt/(-(event.GetWheelDelta()))
282 self.ScrollLines(units*3) 321 self.ScrollLines(units*3)
283 chat_html_window = ChatHtmlWindow 322 chat_html_window = ChatHtmlWindow
491 self.activeplugins = component.get('plugins') 530 self.activeplugins = component.get('plugins')
492 self.parent = parent 531 self.parent = parent
493 # who receives outbound messages, either "all" or "playerid" string 532 # who receives outbound messages, either "all" or "playerid" string
494 self.sendtarget = sendtarget 533 self.sendtarget = sendtarget
495 self.type = tab_type 534 self.type = tab_type
496 self.sound_player = component.get('sound') 535 #self.sound_player = component.get('sound') #Removing!
497 # create die roller manager 536 # create die roller manager
498 self.DiceManager = component.get('DiceManager') 537 #self.DiceManager = component.get('DiceManager') #Removing!
499 # create rpghex tool 538 # create rpghex tool
500 self.r_h = orpg.tools.rgbhex.RGBHex() 539 self.r_h = orpg.tools.rgbhex.RGBHex()
501 self.h = 0 540 self.h = 0
502 self.set_colors() 541 self.set_colors()
503 self.version = VERSION 542 self.version = VERSION
518 self.lastSend = 0 # this is used to help implement the player typing indicator 557 self.lastSend = 0 # this is used to help implement the player typing indicator
519 self.lastPress = 0 # this is used to help implement the player typing indicator 558 self.lastPress = 0 # this is used to help implement the player typing indicator
520 self.Bind(wx.EVT_SIZE, self.OnSize) 559 self.Bind(wx.EVT_SIZE, self.OnSize)
521 self.build_ctrls() 560 self.build_ctrls()
522 #openrpg dir 561 #openrpg dir
523 self.root_dir = dir_struct["home"] 562 #self.root_dir = dir_struct["home"] #Removing!
524 # html font/fontsize is global to all the notebook tabs. 563 # html font/fontsize is global to all the notebook tabs.
525 StartupFont = self.settings.get_setting("defaultfont") 564 StartupFont = self.settings.get_setting("defaultfont")
526 StartupFontSize = self.settings.get_setting("defaultfontsize") 565 StartupFontSize = self.settings.get_setting("defaultfontsize")
527 if(StartupFont != "") and (StartupFontSize != ""): 566 if(StartupFont != "") and (StartupFontSize != ""):
528 try: self.set_default_font(StartupFont, int(StartupFontSize)) 567 try: self.set_default_font(StartupFont, int(StartupFontSize))
1160 self.temptext = "" 1199 self.temptext = ""
1161 self.history = [s] + self.history#prepended instead of appended now, so higher index = greater age 1200 self.history = [s] + self.history#prepended instead of appended now, so higher index = greater age
1162 if not len(macroText): self.chattxt.SetValue("") 1201 if not len(macroText): self.chattxt.SetValue("")
1163 # play sound 1202 # play sound
1164 sound_file = self.settings.get_setting("SendSound") 1203 sound_file = self.settings.get_setting("SendSound")
1165 if sound_file != '': self.sound_player.play(sound_file) 1204 if sound_file != '': component.get('sound').play(sound_file)
1166 if s[0] != "/": ## it's not a slash command 1205 if s[0] != "/": ## it's not a slash command
1167 s = self.ParsePost( s, True, True ) 1206 s = self.ParsePost( s, True, True )
1168 else: self.chat_cmds.docmd(s) # emote is in chatutils.py 1207 else: self.chat_cmds.docmd(s) # emote is in chatutils.py
1169 1208
1170 ## UP KEY 1209 ## UP KEY
1290 if f.ShowModal() == wx.ID_OK: 1329 if f.ShowModal() == wx.ID_OK:
1291 file = open(f.GetPath(), "w") 1330 file = open(f.GetPath(), "w")
1292 file.write(self.ResetPage() + "</body></html>") 1331 file.write(self.ResetPage() + "</body></html>")
1293 file.close() 1332 file.close()
1294 f.Destroy() 1333 f.Destroy()
1295 os.chdir(self.root_dir) 1334 os.chdir(dir_struct["home"])
1296 # def on_chat_save - end 1335 # def on_chat_save - end
1297 1336
1298 @debugging 1337 @debugging
1299 def ResetPage(self): 1338 def ResetPage(self):
1300 self.set_colors() 1339 self.set_colors()
1640 self.SystemPost(text) 1679 self.SystemPost(text)
1641 self.parent.newMsg(0) 1680 self.parent.newMsg(0)
1642 # playe sound 1681 # playe sound
1643 sound_file = self.settings.get_setting(recvSound) 1682 sound_file = self.settings.get_setting(recvSound)
1644 if sound_file != '': 1683 if sound_file != '':
1645 self.sound_player.play(sound_file) 1684 component.get('sound').play(sound_file)
1646 #### Posting helpers ##### 1685 #### Posting helpers #####
1647 1686
1648 @debugging 1687 @debugging
1649 def InfoPost(self, s): 1688 def InfoPost(self, s):
1650 self.Post(self.colorize(self.infocolor, s), c='info') 1689 self.Post(self.colorize(self.infocolor, s), c='info')
1729 log( self.settings, c, name+s2 ) 1768 log( self.settings, c, name+s2 )
1730 else: 1769 else:
1731 newline = "<div class='"+c+"'> " + self.TimeIndexString() + name + s + "</div>" 1770 newline = "<div class='"+c+"'> " + self.TimeIndexString() + name + s + "</div>"
1732 log( self.settings, c, name+s ) 1771 log( self.settings, c, name+s )
1733 else: send = False 1772 else: send = False
1734 newline = chat_util.strip_unicode(newline) 1773 newline = component.get('xml').strip_unicode(newline)
1735 if self.lockscroll == 0: 1774 if self.lockscroll == 0:
1736 self.chatwnd.AppendToPage(newline) 1775 self.chatwnd.AppendToPage(newline)
1737 self.scroll_down() 1776 self.scroll_down()
1738 else: self.storedata.append(newline) 1777 else: self.storedata.append(newline)
1739 if send: 1778 if send:
1818 qmode = 0 1857 qmode = 0
1819 newstr1 = newstr 1858 newstr1 = newstr
1820 if newstr[0].lower() == 'q': 1859 if newstr[0].lower() == 'q':
1821 newstr = newstr[1:] 1860 newstr = newstr[1:]
1822 qmode = 1 1861 qmode = 1
1823 try: newstr = self.DiceManager.proccessRoll(newstr) 1862 try: newstr = component.get('DiceManager').proccessRoll(newstr)
1824 except: pass 1863 except: pass
1825 if qmode == 1: 1864 if qmode == 1:
1826 s = s.replace("[" + matches[i] + "]", "<!-- Official Roll [" + newstr1 + "] => " + newstr + "-->" + newstr, 1) 1865 s = s.replace("[" + matches[i] + "]", "<!-- Official Roll [" + newstr1 + "] => " + newstr + "-->" + newstr, 1)
1827 else: s = s.replace("[" + matches[i] + "]", "[" + newstr1 + "<!-- Official Roll -->] => " + newstr, 1) 1866 else: s = s.replace("[" + matches[i] + "]", "[" + newstr1 + "<!-- Official Roll -->] => " + newstr, 1)
1828 return s 1867 return s