def save(self): """ Save all of the program settings to disk. They must have already been saved into self.data. """ if BibleOrgSysGlobals.debugFlag and debuggingThisModule: print( exp("ApplicationSettings.save() in {!r}").format( self.settingsFilepath)) assert self.data assert self.settingsFilepath BibleOrgSysGlobals.backupAnyExistingFile(self.settingsFilepath, numBackups=4) with open( self.settingsFilepath, 'wt', encoding='utf-8' ) as settingsFile: # It may or may not have previously existed # Put a (comment) heading in the file first settingsFile.write( '# ' + _("{} {} settings file").format(APP_NAME, SettingsVersion) + '\n') settingsFile.write( '# ' + _("Originally saved {} as {}") \ .format( datetime.now().strftime('%Y-%m-%d %H:%M:%S'), self.settingsFilepath ) + '\n\n' ) self.data.write(settingsFile)
def saveAnyChangedGlosses( self, exportAlso=False ): """ Save the glossing dictionary to a pickle file. """ if debuggingThisModule: print( "saveAnyChangedGlosses()" ) if self.haveGlossingDictChanges: BibleOrgSysGlobals.backupAnyExistingFile( self.glossingDictFilepath, 9 ) if BibleOrgSysGlobals.verbosityLevel > 2 or debuggingThisModule: print( " Saving Hebrew glossing dictionary ({}->{} entries) to '{}'…".format( self.loadedGlossEntryCount, len(self.glossingDict), self.glossingDictFilepath ) ) elif BibleOrgSysGlobals.verbosityLevel > 1: print( " Saving Hebrew glossing dictionary ({}->{} entries)…".format( self.loadedGlossEntryCount, len(self.glossingDict) ) ) with open( self.glossingDictFilepath, 'wb' ) as pickleFile: pickle.dump( self.glossingDict, pickleFile ) if exportAlso: self.exportGlossingDictionary()
def save( self ): """ Save all of the program settings to disk. They must have already been saved into self.data. """ if BibleOrgSysGlobals.debugFlag and debuggingThisModule: print( exp("ApplicationSettings.save() in {!r}").format( self.settingsFilepath ) ) assert self.data assert self.settingsFilepath BibleOrgSysGlobals.backupAnyExistingFile( self.settingsFilepath, numBackups=8 ) with open( self.settingsFilepath, 'wt', encoding='utf-8' ) as settingsFile: # It may or may not have previously existed # Put a (comment) heading in the file first settingsFile.write( '# ' + _("{} {} settings file").format( APP_NAME, SettingsVersion ) + '\n' ) settingsFile.write( '# ' + _("Originally saved {} as {}") \ .format( datetime.now().strftime('%Y-%m-%d %H:%M:%S'), self.settingsFilepath ) + '\n\n' ) self.data.write( settingsFile )
def exportGlossingDictionary( self, glossingDictExportFilepath=None ): """ Export the glossing dictionary to a text file plus a reversed text file (without the references). Also does a few checks while exporting. (These can be fixed and then the file can be imported.) """ #print( "exportGlossingDictionary()" ) if glossingDictExportFilepath is None: glossingDictExportFilepath = DEFAULT_GLOSSING_EXPORT_FILEPATH if BibleOrgSysGlobals.verbosityLevel > 1: print( _("Exporting glossing dictionary ({} entries) to '{}'…").format( len(self.glossingDict), glossingDictExportFilepath ) ) BibleOrgSysGlobals.backupAnyExistingFile( glossingDictExportFilepath, 5 ) with open( glossingDictExportFilepath, 'wt' ) as exportFile: for word,(genericGloss,genericReferencesList,specificReferencesDict) in self.glossingDict.items(): if ' ' in word or '/' in word: logging.error( _("Word {!r} has illegal characters").format( word ) ) if ' ' in genericGloss: logging.error( _("Generic gloss {!r} for {!r} has illegal characters").format( genericGloss, word ) ) if word.count('=') != genericGloss.count('='): logging.error( _("Generic gloss {!r} and word {!r} has different numbers of morphemes").format( genericGloss, word ) ) if not genericReferencesList: logging.error( _("Generic gloss {!r} for {!r} has no references").format( genericGloss, word ) ) exportFile.write( '{} {} {} {}\n'.format( genericReferencesList, specificReferencesDict, genericGloss, word ) ) # Works best in editors with English on the left, Hebrew on the right if self.glossingDict: if BibleOrgSysGlobals.verbosityLevel > 1: print( _("Exporting reverse glossing dictionary ({} entries) to '{}'…").format( len(self.glossingDict), DEFAULT_GENERIC_GLOSSING_REVERSE_EXPORT_FILEPATH ) ) BibleOrgSysGlobals.backupAnyExistingFile( DEFAULT_GENERIC_GLOSSING_REVERSE_EXPORT_FILEPATH, 5 ) doneGlosses = [] with open( DEFAULT_GENERIC_GLOSSING_REVERSE_EXPORT_FILEPATH, 'wt' ) as exportFile: for word,(genericGloss,genericReferencesList,specificReferencesDict) in sorted( self.glossingDict.items(), key=lambda theTuple: theTuple[1][0].lower() ): if genericGloss in doneGlosses: logging.warning( _("Generic gloss {!r} has already appeared: currently for word {!r}").format( genericGloss, word ) ) exportFile.write( '{} {}\n'.format( genericGloss, word ) ) # Works best in editors with English on the left, Hebrew on the right doneGlosses.append( genericGloss )
def searchReplaceText( self, optionsDict, confirmCallback ): """ Search the Bible book files for the given text which is contained in a dictionary of options. Search string must be in optionsDict['searchText']. (We add default options for any missing ones as well as updating the 'searchHistoryList'.) Then go through and replace. "self" in this case is either a USFMBible or a PTXBible object. The confirmCallback function must be a function that takes 6 parameters: ref, contextBefore, ourSearchText, contextAfter, willBeText, haveUndosFlag and returns a single UPPERCASE character 'N' (no), 'Y' (yes), 'A' (all), or 'S' (stop). Note that this function works on actual text files. If the text files are loaded into a Bible object, after replacing, the Bible object will need to be reloaded. If it's called from an edit window, it's essential that all editing changes are saved to the file first. NOTE: We currently handle undo, by cache all files which need to be saved to disk. We might need to make this more efficient, e.g., save under a temp filename. """ if BibleOrgSysGlobals.debugFlag: if debuggingThisModule: print( exp("searchReplaceText( {}, {}, … )").format( self, optionsDict ) ) assert 'searchText' in optionsDict assert 'replaceText' in optionsDict optionsList = ( 'searchText', 'replaceText', 'work', 'searchHistoryList', 'replaceHistoryList', 'wordMode', #'caselessFlag', 'ignoreDiacriticsFlag', 'includeIntroFlag', 'includeMainTextFlag', #'includeMarkerTextFlag', 'includeExtrasFlag', 'markerList', 'chapterList', 'contextLength', 'bookList', 'regexFlag', 'currentBCV', 'doBackups', ) for someKey in optionsDict: if someKey not in optionsList: print( "searchReplaceText warning: unexpected {!r} option = {!r}".format( someKey, optionsDict[someKey] ) ) if debuggingThisModule: halt # Go through all the given options if 'work' not in optionsDict: optionsDict['work'] = self.abbreviation if self.abbreviation else self.name if 'searchHistoryList' not in optionsDict: optionsDict['searchHistoryList'] = [] # Oldest first if 'wordMode' not in optionsDict: optionsDict['wordMode'] = 'Any' # or 'Whole' or 'EndsWord' or 'Begins' or 'EndsLine' #if 'caselessFlag' not in optionsDict: optionsDict['caselessFlag'] = True #if 'ignoreDiacriticsFlag' not in optionsDict: optionsDict['ignoreDiacriticsFlag'] = False #if 'includeIntroFlag' not in optionsDict: optionsDict['includeIntroFlag'] = True #if 'includeMainTextFlag' not in optionsDict: optionsDict['includeMainTextFlag'] = True #if 'includeMarkerTextFlag' not in optionsDict: optionsDict['includeMarkerTextFlag'] = False #if 'includeExtrasFlag' not in optionsDict: optionsDict['includeExtrasFlag'] = False if 'contextLength' not in optionsDict: optionsDict['contextLength'] = 60 # each side if 'bookList' not in optionsDict: optionsDict['bookList'] = 'ALL' # or BBB or a list #if 'chapterList' not in optionsDict: optionsDict['chapterList'] = None #if 'markerList' not in optionsDict: optionsDict['markerList'] = None if 'doBackups' not in optionsDict: optionsDict['doBackups'] = True optionsDict['regexFlag'] = False if BibleOrgSysGlobals.debugFlag: if optionsDict['chapterList']: assert optionsDict['bookList'] is None or len(optionsDict['bookList']) == 1 \ or optionsDict['chapterList'] == [0] # Only combinations that make sense assert '\r' not in optionsDict['searchText'] and '\n' not in optionsDict['searchText'] assert optionsDict['wordMode'] in ( 'Any', 'Whole', 'Begins', 'EndsWord', 'EndsLine', ) if optionsDict['wordMode'] != 'Any': assert ' ' not in optionsDict['searchText'] #if optionsDict['markerList']: #assert isinstance( markerList, list ) #assert not optionsDict['includeIntroFlag'] #assert not optionsDict['includeMainTextFlag'] #assert not optionsDict['includeMarkerTextFlag'] #assert not optionsDict['includeExtrasFlag'] #ourMarkerList = [] #if optionsDict['markerList']: #for marker in optionsDict['markerList']: #ourMarkerList.append( BibleOrgSysGlobals.USFMMarkers.toStandardMarker( marker ) ) resultDict = { 'numFinds':0, 'numReplaces':0, 'searchedBookList':[], 'foundBookList':[], 'replacedBookList':[], 'aborted':False, } ourSearchText = optionsDict['searchText'] # Save the search history (with the 'regex:' text still prefixed if applicable) try: optionsDict['searchHistoryList'].remove( ourSearchText ) except ValueError: pass optionsDict['searchHistoryList'].append( ourSearchText ) # Make sure it goes on the end ourReplaceText = optionsDict['replaceText'] try: optionsDict['replaceHistoryList'].remove( ourReplaceText ) except ValueError: pass optionsDict['replaceHistoryList'].append( ourReplaceText ) # Make sure it goes on the end if ourSearchText.lower().startswith( 'regex:' ): resultDict['hadRegexError'] = False optionsDict['regexFlag'] = True ourSearchText = ourSearchText[6:] compiledSearchText = re.compile( ourSearchText ) else: replaceLen = len( ourReplaceText ) #diffLen = replaceLen - searchLen #if optionsDict['ignoreDiacriticsFlag']: ourSearchText = BibleOrgSysGlobals.removeAccents( ourSearchText ) #if optionsDict['caselessFlag']: ourSearchText = ourSearchText.lower() searchLen = len( ourSearchText ) if BibleOrgSysGlobals.debugFlag: assert searchLen #print( " Searching for {!r} in {} loaded books".format( ourSearchText, len(self) ) ) if not self.preloadDone: self.preload() # The first entry in the result list is a dictionary containing the parameters # Following entries are SimpleVerseKey objects encoding = self.encoding if encoding is None: encoding = 'utf-8' replaceAllFlag = stopFlag = undoFlag = False filesToSave = OrderedDict() if self.maximumPossibleFilenameTuples: for BBB,filename in self.maximumPossibleFilenameTuples: if optionsDict['bookList'] is None or optionsDict['bookList']=='ALL' or BBB in optionsDict['bookList']: #print( exp("searchReplaceText: will search book {}").format( BBB ) ) bookFilepath = os.path.join( self.sourceFolder, filename ) with open( bookFilepath, 'rt', encoding=encoding ) as bookFile: bookText = bookFile.read() resultDict['searchedBookList'].append( BBB ) #C = V = '0' if optionsDict['regexFlag']: # ignores wordMode flag ix = 0 while True: match = compiledSearchText.search( bookText, ix ) if not match: break # none / no more found ix, ixAfter = match.span() regexFoundText = bookText[ix:ixAfter] try: regexReplacementText = compiledSearchText.sub( ourReplaceText, regexFoundText, count=1 ) except re.error as err: print( "Search/Replace regex error: {}".format( err ) ) resultDict['hadRegexError'] = True stopFlag = True; break #print( "Found regex {!r} at {:,} in {}".format( ourSearchText, ix, BBB ) ) #print( " Found text was {!r}, replacement will be {!r}".format( regexFoundText, regexReplacementText ) ) resultDict['numFinds'] += 1 if BBB not in resultDict['foundBookList']: resultDict['foundBookList'].append( BBB ) if optionsDict['contextLength']: # Find the context in the original (fully-cased) string contextBefore = bookText[max(0,ix-optionsDict['contextLength']):ix] contextAfter = bookText[ixAfter:ixAfter+optionsDict['contextLength']] else: contextBefore = contextAfter = None #print( " After {!r}".format( contextBefore ) ) #print( " Before {!r}".format( contextAfter ) ) result = None if not replaceAllFlag: ref = BBB willBeText = contextBefore + regexReplacementText + contextAfter result = confirmCallback( ref, contextBefore, regexFoundText, contextAfter, willBeText, resultDict['numReplaces']>0 ) #print( "searchReplaceText got", result ) assert result in 'YNASU' if result == 'A': replaceAllFlag = True elif result == 'S': stopFlag = True; break elif result == 'U': undoFlag = True; break if replaceAllFlag or result == 'Y': #print( " ix={:,}, ixAfter={:,}, diffLen={}".format( ix, ixAfter, diffLen ) ) bookText = bookText[:ix] + regexReplacementText + bookText[ixAfter:] ix += len( regexReplacementText ) # Start searching after the replacement #print( " ix={:,}, ixAfter={:,}, now={!r}".format( ix, ixAfter, bookText ) ) resultDict['numReplaces'] += 1 if BBB not in resultDict['replacedBookList']: resultDict['replacedBookList'].append( BBB ) filesToSave[BBB] = (bookFilepath,bookText) else: ix += 1 # So don't keep repeating the same find else: # not regExp ix = 0 while True: ix = bookText.find( ourSearchText, ix ) if ix == -1: break # none / no more found #print( "Found {!r} at {:,} in {}".format( ourSearchText, ix, BBB ) ) resultDict['numFinds'] += 1 if BBB not in resultDict['foundBookList']: resultDict['foundBookList'].append( BBB ) ixAfter = ix + searchLen if optionsDict['wordMode'] == 'Whole': #print( "BF", repr(bookText[ix-1]) ) #print( "AF", repr(bookText[ixAfter]) ) if ix>0 and bookText[ix-1].isalpha(): ix+=1; continue if ixAfter<len(bookText) and bookText[ixAfter].isalpha(): ix+=1; continue elif optionsDict['wordMode'] == 'Begins': if ix>0 and bookText[ix-1].isalpha(): ix+=1; continue elif optionsDict['wordMode'] == 'EndsWord': if ixAfter<len(bookText) and bookText[ixAfter].isalpha(): ix+=1; continue elif optionsDict['wordMode'] == 'EndsLine': if ixAfter<len(bookText): ix+=1; continue if optionsDict['contextLength']: # Find the context in the original (fully-cased) string contextBefore = bookText[max(0,ix-optionsDict['contextLength']):ix] contextAfter = bookText[ixAfter:ixAfter+optionsDict['contextLength']] else: contextBefore = contextAfter = None #print( " After {!r}".format( contextBefore ) ) #print( " Before {!r}".format( contextAfter ) ) result = None if not replaceAllFlag: ref = BBB willBeText = contextBefore + ourReplaceText + contextAfter result = confirmCallback( ref, contextBefore, ourSearchText, contextAfter, willBeText, resultDict['numReplaces']>0 ) #print( "searchReplaceText got", result ) assert result in 'YNASU' if result == 'A': replaceAllFlag = True elif result == 'S': stopFlag = True; break elif result == 'U': undoFlag = True; break if replaceAllFlag or result == 'Y': #print( " ix={:,}, ixAfter={:,}, diffLen={}".format( ix, ixAfter, diffLen ) ) bookText = bookText[:ix] + ourReplaceText + bookText[ixAfter:] ix += replaceLen # Start searching after the replacement #print( " ix={:,}, ixAfter={:,}, now={!r}".format( ix, ixAfter, bookText ) ) resultDict['numReplaces'] += 1 if BBB not in resultDict['replacedBookList']: resultDict['replacedBookList'].append( BBB ) filesToSave[BBB] = (bookFilepath,bookText) else: ix += 1 # So don't keep repeating the same find if stopFlag: if BibleOrgSysGlobals.verbosityLevel > 2: print( "Search/Replace was aborted in {} after {} replaces.".format( BBB, resultDict['numReplaces'] ) ) resultDict['aborted'] = True break if undoFlag: if resultDict['numReplaces']>0: if BibleOrgSysGlobals.verbosityLevel > 2: print( "Search/Replace was aborted in {} for undo in {} books.".format( BBB, len(resultDict['replacedBookList']) ) ) elif BibleOrgSysGlobals.verbosityLevel > 2: print( "Search/Replace was aborted (by undo) in {}.".format( BBB ) ) filesToSave = {} resultDict['replacedBookList'] = [] resultDict['numReplaces'] = 0 resultDict['aborted'] = True break else: logging.critical( exp("No book files to search/replace in {}!").format( self.sourceFolder ) ) for BBB,(filepath,fileText) in filesToSave.items(): if optionsDict['doBackups']: if BibleOrgSysGlobals.verbosityLevel > 2: print( "Making backup copy of {} file: {}…".format( BBB, filepath ) ) BibleOrgSysGlobals.backupAnyExistingFile( filepath, numBackups=4 ) if BibleOrgSysGlobals.verbosityLevel > 2: print( "Writing {:,} bytes for {} to {}…".format( len(fileText), BBB, filepath ) ) elif BibleOrgSysGlobals.verbosityLevel > 1: print( "Saving {} with {} encoding".format( filepath, encoding ) ) with open( filepath, 'wt', encoding=encoding, newline='\r\n' ) as bookFile: bookFile.write( fileText ) self.bookNeedsReloading[BBB] = True #print( exp("searchReplaceText: returning {}/{} {}/{}/{} books {}").format( resultDict['numReplaces'], resultDict['numFinds'], len(resultDict['replacedBookList']), len(resultDict['foundBookList']), len(resultDict['searchedBookList']), optionsDict ) ) return optionsDict, resultDict
def findReplaceText(self, optionsDict, confirmCallback): """ Search the Bible book files for the given text which is contained in a dictionary of options. Find string must be in optionsDict['findText']. (We add default options for any missing ones as well as updating the 'findHistoryList'.) Then go through and replace. "self" in this case is either a USFMBible or a PTXBible object. The confirmCallback function must be a function that takes 6 parameters: ref, contextBefore, ourFindText, contextAfter, willBeText, haveUndosFlag and returns a single UPPERCASE character 'N' (no), 'Y' (yes), 'A' (all), or 'S' (stop). Note that this function works on actual text files. If the text files are loaded into a Bible object, after replacing, the Bible object will need to be reloaded. If it's called from an edit window, it's essential that all editing changes are saved to the file first. NOTE: We currently handle undo, by caching all files which need to be saved to disk. We might need to make this more efficient, e.g., save under a temp filename. """ if BibleOrgSysGlobals.debugFlag: if debuggingThisModule: print( exp("findReplaceText( {}, {}, … )").format(self, optionsDict)) assert 'findText' in optionsDict assert 'replaceText' in optionsDict optionsList = ( 'parentApp', 'parentWindow', 'parentBox', 'givenBible', 'workName', 'findText', 'replaceText', 'findHistoryList', 'replaceHistoryList', 'wordMode', #'caselessFlag', 'ignoreDiacriticsFlag', 'includeIntroFlag', 'includeMainTextFlag', #'includeMarkerTextFlag', 'includeExtrasFlag', 'markerList', 'chapterList', 'contextLength', 'bookList', 'regexFlag', 'currentBCV', 'doBackups', ) for someKey in optionsDict: if someKey not in optionsList: print("findReplaceText warning: unexpected {!r} option = {!r}". format(someKey, optionsDict[someKey])) if debuggingThisModule: halt # Go through all the given options if 'workName' not in optionsDict: optionsDict[ 'workName'] = self.abbreviation if self.abbreviation else self.name if 'findHistoryList' not in optionsDict: optionsDict['findHistoryList'] = [] # Oldest first if 'wordMode' not in optionsDict: optionsDict[ 'wordMode'] = 'Any' # or 'Whole' or 'EndsWord' or 'Begins' or 'EndsLine' #if 'caselessFlag' not in optionsDict: optionsDict['caselessFlag'] = True #if 'ignoreDiacriticsFlag' not in optionsDict: optionsDict['ignoreDiacriticsFlag'] = False #if 'includeIntroFlag' not in optionsDict: optionsDict['includeIntroFlag'] = True #if 'includeMainTextFlag' not in optionsDict: optionsDict['includeMainTextFlag'] = True #if 'includeMarkerTextFlag' not in optionsDict: optionsDict['includeMarkerTextFlag'] = False #if 'includeExtrasFlag' not in optionsDict: optionsDict['includeExtrasFlag'] = False if 'contextLength' not in optionsDict: optionsDict['contextLength'] = 60 # each side if 'bookList' not in optionsDict: optionsDict['bookList'] = 'ALL' # or BBB or a list #if 'chapterList' not in optionsDict: optionsDict['chapterList'] = None #if 'markerList' not in optionsDict: optionsDict['markerList'] = None if 'doBackups' not in optionsDict: optionsDict['doBackups'] = True optionsDict['regexFlag'] = False if BibleOrgSysGlobals.debugFlag: if optionsDict['chapterList']: assert optionsDict['bookList'] is None or len(optionsDict['bookList']) == 1 \ or optionsDict['chapterList'] == [0] # Only combinations that make sense assert '\r' not in optionsDict['findText'] and '\n' not in optionsDict[ 'findText'] assert optionsDict['wordMode'] in ( 'Any', 'Whole', 'Begins', 'EndsWord', 'EndsLine', ) if optionsDict['wordMode'] != 'Any': assert ' ' not in optionsDict['findText'] #if optionsDict['markerList']: #assert isinstance( markerList, list ) #assert not optionsDict['includeIntroFlag'] #assert not optionsDict['includeMainTextFlag'] #assert not optionsDict['includeMarkerTextFlag'] #assert not optionsDict['includeExtrasFlag'] #ourMarkerList = [] #if optionsDict['markerList']: #for marker in optionsDict['markerList']: #ourMarkerList.append( BibleOrgSysGlobals.USFMMarkers.toStandardMarker( marker ) ) resultDict = { 'numFinds': 0, 'numReplaces': 0, 'searchedBookList': [], 'foundBookList': [], 'replacedBookList': [], 'aborted': False, } ourFindText = optionsDict['findText'] # Save the search history (with the 'regex:' text still prefixed if applicable) try: optionsDict['findHistoryList'].remove(ourFindText) except ValueError: pass optionsDict['findHistoryList'].append( ourFindText) # Make sure it goes on the end ourReplaceText = optionsDict['replaceText'] try: optionsDict['replaceHistoryList'].remove(ourReplaceText) except ValueError: pass optionsDict['replaceHistoryList'].append( ourReplaceText) # Make sure it goes on the end if ourFindText.lower().startswith('regex:'): resultDict['hadRegexError'] = False optionsDict['regexFlag'] = True ourFindText = ourFindText[6:] compiledFindText = re.compile(ourFindText) else: replaceLen = len(ourReplaceText) #diffLen = replaceLen - searchLen #if optionsDict['ignoreDiacriticsFlag']: ourFindText = BibleOrgSysGlobals.removeAccents( ourFindText ) #if optionsDict['caselessFlag']: ourFindText = ourFindText.lower() searchLen = len(ourFindText) if BibleOrgSysGlobals.debugFlag: assert searchLen #print( " Searching for {!r} in {} loaded books".format( ourFindText, len(self) ) ) if not self.preloadDone: self.preload() # The first entry in the result list is a dictionary containing the parameters # Following entries are SimpleVerseKey objects encoding = self.encoding if encoding is None: encoding = 'utf-8' replaceAllFlag = stopFlag = undoFlag = False filesToSave = OrderedDict() if self.maximumPossibleFilenameTuples: for BBB, filename in self.maximumPossibleFilenameTuples: if optionsDict['bookList'] is None or optionsDict[ 'bookList'] == 'ALL' or BBB in optionsDict['bookList']: #print( exp("findReplaceText: will search book {}").format( BBB ) ) bookFilepath = os.path.join(self.sourceFolder, filename) with open(bookFilepath, 'rt', encoding=encoding) as bookFile: bookText = bookFile.read() resultDict['searchedBookList'].append(BBB) #C = V = '0' if optionsDict['regexFlag']: # ignores wordMode flag ix = 0 while True: match = compiledFindText.search(bookText, ix) if not match: break # none / no more found ix, ixAfter = match.span() regexFoundText = bookText[ix:ixAfter] try: regexReplacementText = compiledFindText.sub( ourReplaceText, regexFoundText, count=1) except re.error as err: print("Search/Replace regex error: {}".format(err)) resultDict['hadRegexError'] = True stopFlag = True break #print( "Found regex {!r} at {:,} in {}".format( ourFindText, ix, BBB ) ) #print( " Found text was {!r}, replacement will be {!r}".format( regexFoundText, regexReplacementText ) ) resultDict['numFinds'] += 1 if BBB not in resultDict['foundBookList']: resultDict['foundBookList'].append(BBB) if optionsDict[ 'contextLength']: # Find the context in the original (fully-cased) string contextBefore = bookText[ max(0, ix - optionsDict['contextLength']):ix] contextAfter = bookText[ ixAfter:ixAfter + optionsDict['contextLength']] else: contextBefore = contextAfter = None #print( " After {!r}".format( contextBefore ) ) #print( " Before {!r}".format( contextAfter ) ) result = None if not replaceAllFlag: ref = BBB willBeText = contextBefore + regexReplacementText + contextAfter result = confirmCallback( ref, contextBefore, regexFoundText, contextAfter, willBeText, resultDict['numReplaces'] > 0) #print( "findReplaceText got", result ) assert result in 'YNASU' if result == 'A': replaceAllFlag = True elif result == 'S': stopFlag = True break elif result == 'U': undoFlag = True break if replaceAllFlag or result == 'Y': #print( " ix={:,}, ixAfter={:,}, diffLen={}".format( ix, ixAfter, diffLen ) ) bookText = bookText[: ix] + regexReplacementText + bookText[ ixAfter:] ix += len( regexReplacementText ) # Start searching after the replacement #print( " ix={:,}, ixAfter={:,}, now={!r}".format( ix, ixAfter, bookText ) ) resultDict['numReplaces'] += 1 if BBB not in resultDict['replacedBookList']: resultDict['replacedBookList'].append(BBB) filesToSave[BBB] = (bookFilepath, bookText) else: ix += 1 # So don't keep repeating the same find else: # not regExp ix = 0 while True: ix = bookText.find(ourFindText, ix) if ix == -1: break # none / no more found #print( "Found {!r} at {:,} in {}".format( ourFindText, ix, BBB ) ) resultDict['numFinds'] += 1 if BBB not in resultDict['foundBookList']: resultDict['foundBookList'].append(BBB) ixAfter = ix + searchLen if optionsDict['wordMode'] == 'Whole': #print( "BF", repr(bookText[ix-1]) ) #print( "AF", repr(bookText[ixAfter]) ) if ix > 0 and bookText[ix - 1].isalpha(): ix += 1 continue if ixAfter < len( bookText) and bookText[ixAfter].isalpha(): ix += 1 continue elif optionsDict['wordMode'] == 'Begins': if ix > 0 and bookText[ix - 1].isalpha(): ix += 1 continue elif optionsDict['wordMode'] == 'EndsWord': if ixAfter < len( bookText) and bookText[ixAfter].isalpha(): ix += 1 continue elif optionsDict['wordMode'] == 'EndsLine': if ixAfter < len(bookText): ix += 1 continue if optionsDict[ 'contextLength']: # Find the context in the original (fully-cased) string contextBefore = bookText[ max(0, ix - optionsDict['contextLength']):ix] contextAfter = bookText[ ixAfter:ixAfter + optionsDict['contextLength']] else: contextBefore = contextAfter = None #print( " After {!r}".format( contextBefore ) ) #print( " Before {!r}".format( contextAfter ) ) result = None if not replaceAllFlag: ref = BBB willBeText = contextBefore + ourReplaceText + contextAfter result = confirmCallback( ref, contextBefore, ourFindText, contextAfter, willBeText, resultDict['numReplaces'] > 0) #print( "findReplaceText got", result ) assert result in 'YNASU' if result == 'A': replaceAllFlag = True elif result == 'S': stopFlag = True break elif result == 'U': undoFlag = True break if replaceAllFlag or result == 'Y': #print( " ix={:,}, ixAfter={:,}, diffLen={}".format( ix, ixAfter, diffLen ) ) bookText = bookText[: ix] + ourReplaceText + bookText[ ixAfter:] ix += replaceLen # Start searching after the replacement #print( " ix={:,}, ixAfter={:,}, now={!r}".format( ix, ixAfter, bookText ) ) resultDict['numReplaces'] += 1 if BBB not in resultDict['replacedBookList']: resultDict['replacedBookList'].append(BBB) filesToSave[BBB] = (bookFilepath, bookText) else: ix += 1 # So don't keep repeating the same find if stopFlag: if BibleOrgSysGlobals.verbosityLevel > 2: print( "Search/Replace was aborted in {} after {} replaces.". format(BBB, resultDict['numReplaces'])) resultDict['aborted'] = True break if undoFlag: if resultDict['numReplaces'] > 0: if BibleOrgSysGlobals.verbosityLevel > 2: print( "Search/Replace was aborted in {} for undo in {} books." .format(BBB, len(resultDict['replacedBookList']))) elif BibleOrgSysGlobals.verbosityLevel > 2: print("Search/Replace was aborted (by undo) in {}.".format( BBB)) filesToSave = OrderedDict() resultDict['replacedBookList'] = [] resultDict['numReplaces'] = 0 resultDict['aborted'] = True break else: logging.critical( exp("No book files to search/replace in {}!").format( self.sourceFolder)) for BBB, (filepath, fileText) in filesToSave.items(): if optionsDict['doBackups']: if BibleOrgSysGlobals.verbosityLevel > 2: print("Making backup copy of {} file: {}…".format( BBB, filepath)) BibleOrgSysGlobals.backupAnyExistingFile(filepath, numBackups=4) if BibleOrgSysGlobals.verbosityLevel > 2: print("Writing {:,} bytes for {} to {}…".format( len(fileText), BBB, filepath)) elif BibleOrgSysGlobals.verbosityLevel > 1: print("Saving {} with {} encoding".format(filepath, encoding)) with open(filepath, 'wt', encoding=encoding, newline='\r\n') as bookFile: bookFile.write(fileText) self.bookNeedsReloading[BBB] = True #print( exp("findReplaceText: returning {}/{} {}/{}/{} books {}").format( resultDict['numReplaces'], resultDict['numFinds'], len(resultDict['replacedBookList']), len(resultDict['foundBookList']), len(resultDict['searchedBookList']), optionsDict ) ) return optionsDict, resultDict