Version 0.8.0-beta
- handle replacement via slash command - emote and ooc detection - de-/activate single mappings - move buttons and mappings are disabled when already at top/bottom or deactivated - minimap button is darkened when addon is disabled - help tab with examples - right-click on minimap button quickly de-/activates replacements - fixed mapping to raid warning, instance and battleground chats - localized raid target markers - capital % substitutions - incorrect consolidation - Umlaut and accent replacements
This commit is contained in:
@@ -2,44 +2,132 @@
|
||||
local _G = _G
|
||||
local Grichelde = _G.Grichelde
|
||||
|
||||
local IsAddOnLoaded, nilOrEmpty, ipairs, spairs, tContains, tFilter, tInsert, tConcat, find, sub, isUpper, isLower, toUpper, toLower, trim, length
|
||||
= Grichelde.functions.IsAddOnLoaded, Grichelde.functions.nilOrEmpty, Grichelde.functions.ipairs, Grichelde.functions.spairs, Grichelde.functions.tContains, Grichelde.functions.tFilter, Grichelde.functions.tInsert, Grichelde.functions.tConcat,
|
||||
Grichelde.functions.find, Grichelde.functions.sub, Grichelde.functions.isUpper, Grichelde.functions.isLower, Grichelde.functions.toUpper, Grichelde.functions.toLower, Grichelde.functions.trim, Grichelde.functions.length
|
||||
local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tConcat, tSize, tIsEmpty, find, sub, isUpper, isLower, toUpper, toLower, trim, length, lenUtf8
|
||||
= Grichelde.functions.IsAddOnLoaded, Grichelde.functions.assert, Grichelde.functions.nilOrEmpty, Grichelde.functions.pairs, Grichelde.functions.ipairs, Grichelde.functions.spairs, Grichelde.functions.tContains, Grichelde.functions.tFilter, Grichelde.functions.tInsert, Grichelde.functions.tConcat, Grichelde.functions.tSize, Grichelde.functions.tIsEmpty,
|
||||
Grichelde.functions.find, Grichelde.functions.sub, Grichelde.functions.isUpper, Grichelde.functions.isLower, Grichelde.functions.toUpper, Grichelde.functions.toLower, Grichelde.functions.trim, Grichelde.functions.length, Grichelde.functions.lenUtf8
|
||||
|
||||
local function cleanseMessage(this, message)
|
||||
local text = message or ""
|
||||
if (IsAddOnLoaded("Misspelled")) then
|
||||
this:DebugPrint("Misspelled detected: cleansing message")
|
||||
text = _G.Misspelled:RemoveHighlighting(message)
|
||||
end
|
||||
return trim(text)
|
||||
end
|
||||
|
||||
--- Before a chat message is sent, check if replacement is required and replace the text accordingly.
|
||||
-- @param message string
|
||||
-- @param type string
|
||||
-- @param language string
|
||||
-- @param channel string
|
||||
function Grichelde:SendChatMessage(message, type, language, channel, ...)
|
||||
local replacedText = self:CheckAndReplace(message, type)
|
||||
|
||||
|
||||
-- Send text in chunks if length exceeds 255 bytes after replacement
|
||||
local chunks = self:SplitText(replacedText)
|
||||
self:DebugPrint("SendChatMessage : #chunks:", #chunks)
|
||||
|
||||
for _, chunk in ipairs(chunks) do
|
||||
self.hooks["SendChatMessage"](chunk, type, language, channel, ...);
|
||||
function Grichelde:SendChatMessage(message, type, ...)
|
||||
local text = cleanseMessage(self, message)
|
||||
if (self:CheckReplacementAllowed(text, type)) then
|
||||
text = self:ReplaceText(text)
|
||||
end
|
||||
|
||||
self:SendChunkifiedChatMessage(text, type, ...)
|
||||
end
|
||||
|
||||
function Grichelde:CheckAndReplace(message, type)
|
||||
local text = message
|
||||
if (self:CheckReplacement(text, type)) then
|
||||
if (IsAddOnLoaded("Misspelled")) then
|
||||
self:DebugPrint("Misspelled detected: cleansing message")
|
||||
text = _G.Misspelled:RemoveHighlighting(text)
|
||||
--- Always replaces the text accoording to the configuration, even if activation or channel was disabled.
|
||||
--- This is used the the override slash command: "/gri /emote text to replace".
|
||||
--- NOTE: type and channel (in case of whispers) are determined from the message text.
|
||||
-- @param message string
|
||||
-- @param type string
|
||||
function Grichelde:SendChatMessageOverride(message, ...)
|
||||
local text = cleanseMessage(self, message)
|
||||
|
||||
local chatType, lang, channel = text, DEFAULT_CHAT_FRAME.editBox.chatType or "SAY", DEFAULT_CHAT_FRAME.editBox.languageID
|
||||
local msg, type, chan = self:CheckAndExtractMessageTypeTarget(text)
|
||||
|
||||
if msg ~= nil then
|
||||
msg = self:ReplaceText(msg)
|
||||
|
||||
if type ~= nil then
|
||||
self:SendChunkifiedChatMessage(msg, type, lang, chan, ...)
|
||||
else
|
||||
self:SendChunkifiedChatMessage(msg, chatType, lang, channel, ...)
|
||||
end
|
||||
text = self:ReplaceText(trim(text))
|
||||
else
|
||||
-- suppress invalid messages/channels/targets
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
function Grichelde:CheckReplacement(text, channel)
|
||||
-- skip if not enabled
|
||||
--- Send text in chunks if length exceeds 255 bytes after replacement.
|
||||
function Grichelde:SendChunkifiedChatMessage(message, ...)
|
||||
if length(message) > 255 then
|
||||
local chunks = self:SplitText(message)
|
||||
self:DebugPrint("SendChatMessage : #chunks:", #chunks)
|
||||
|
||||
for _, chunk in ipairs(chunks) do
|
||||
self.hooks["SendChatMessage"](chunk, ...);
|
||||
end
|
||||
else
|
||||
self.hooks["SendChatMessage"](message, ...);
|
||||
end
|
||||
end
|
||||
|
||||
local function IsOneBigEmote(this, text)
|
||||
local firstWord, _ = this:SplitOnFirstMatch(text)
|
||||
assert(firstWord ~= nil, "firstWord is never nil")
|
||||
|
||||
-- emote detection
|
||||
local isOneBigEmote = false
|
||||
-- scheme *emote*
|
||||
if sub(firstWord, 1, 1) == "<" then
|
||||
-- search for emote end
|
||||
local _, emoteEnd = find(text, "%>", 2)
|
||||
isOneBigEmote = (emoteEnd == length(text))
|
||||
end
|
||||
if not isOneBigEmote and sub(firstWord, 1, 1) == "*" then
|
||||
-- search for emote end
|
||||
local _, emoteEnd = find(text, "%*", 2)
|
||||
isOneBigEmote = (emoteEnd == length(text))
|
||||
end
|
||||
-- scheme **emote**
|
||||
if not isOneBigEmote and sub(firstWord, 1, 2) == "**" then
|
||||
-- search for emote end
|
||||
local _, emoteEnd = find(text, "%*%*", 3)
|
||||
isOneBigEmote = (emoteEnd == length(text))
|
||||
end
|
||||
|
||||
-- the whole text is one big emote
|
||||
return isOneBigEmote
|
||||
end
|
||||
|
||||
--[[
|
||||
--- Detect OOC in text, patterns are (( ooc )) or ooc:
|
||||
local function IsOoc(this, text)
|
||||
local firstWord, _ = this:SplitOnFirstMatch(text)
|
||||
assert(firstWord ~= nil, "firstWord is never nil")
|
||||
|
||||
-- scheme: (( ooc ))
|
||||
if sub(firstWord, 1, 2) == "((" then
|
||||
-- search for emote end
|
||||
local _, oocEnd = find(text, "%)%)", 3)
|
||||
if (oocEnd == length(text)) then
|
||||
this:TracePrintPrint("IsOoc : skip ((ooc))", text)
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- scheme: ooc:
|
||||
if sub(firstWord, 1, 4) == "ooc:" then
|
||||
this:TracePrint("IsOoc : skip ooc:", text)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
]]
|
||||
|
||||
--- Checks if a message can be replaced according to configuration.
|
||||
-- @return boolean
|
||||
function Grichelde:CheckReplacementAllowed(text, channel)
|
||||
self:DebugPrint("CheckReplacementAllowed : text:", text)
|
||||
|
||||
-- skip if disabled
|
||||
if (not self.db.profile.enabled) then
|
||||
self:DebugPrint("CheckReplacement : disabled")
|
||||
self:DebugPrint("CheckReplacementAllowed : disabled")
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -49,35 +137,139 @@ function Grichelde:CheckReplacement(text, channel)
|
||||
end
|
||||
|
||||
-- skip if wrong channel
|
||||
local chan = self:ConvertBlizzTypeToOption(channel)
|
||||
local allowedChannels = tFilter(self.db.profile.channels,
|
||||
function(_,v) return v == true end,
|
||||
function(k,_) return k end
|
||||
function(_, k, v) return k == chan and v == true end,
|
||||
function(_, k, _) return k end
|
||||
)
|
||||
self:DebugPrint("CheckReplacement : allowed channels:", tConcat(allowedChannels, ", "))
|
||||
local type = self:ConvertBlizChannelToType(channel)
|
||||
if (type == nil or not tContains(allowedChannels, type)) then
|
||||
self:DebugPrint("CheckReplacement : skip channel type:", type)
|
||||
self:DebugPrint("CheckReplacementAllowed : allowed channels:")
|
||||
self:DebugPrint(allowedChannels)
|
||||
|
||||
if tIsEmpty(allowedChannels) then
|
||||
self:DebugPrint("CheckReplacementAllowed : skip channel type:", chan)
|
||||
return false
|
||||
end
|
||||
|
||||
-- don't replace slash commands except chat related commands
|
||||
if sub(text, 1, 1) == "/" then
|
||||
local firstWord, _ = self:SplitOnFirstMatch(text)
|
||||
-- todo: adapt allowed slash commands
|
||||
if (firstWord == nil or not tContains(self.slashCommands, firstWord)) then
|
||||
self:DebugPrint("CheckReplacement : ignore slash command")
|
||||
return false
|
||||
end
|
||||
local firstWord, _ = self:SplitOnFirstMatch(text)
|
||||
assert(firstWord ~= nil, "firstWord is never nil")
|
||||
|
||||
-- don't replace slash commands
|
||||
if sub(firstWord, 1, 1) == "/" then
|
||||
self:DebugPrint("CheckReplacementAllowed : skip other slash commands:", firstWord)
|
||||
return false
|
||||
end
|
||||
|
||||
-- in any other case
|
||||
-- emote detection
|
||||
if IsOneBigEmote(self, text) then
|
||||
self:DebugPrint("CheckReplacementAllowed : one big emote")
|
||||
return self.db.profile.channels.emote
|
||||
end
|
||||
|
||||
-- in any other case, treat as ordinary text or emote
|
||||
return true
|
||||
end
|
||||
|
||||
function Grichelde:ConvertBlizChannelToType(channel)
|
||||
local type = toLower(channel)
|
||||
self:DebugPrint("ConvertBlizChannelToType : convert %s to %s", channel, type)
|
||||
return type
|
||||
--- Checks if the text from the Grichelde slash command can be replaced and if so
|
||||
--- returns the replacable text, the chat type and target (player or channel) from the message text.
|
||||
--- This is used the the override slash command: "/gri /emote text to replace".
|
||||
-- @param text (string) the whole message
|
||||
-- @return message (string) stripped message
|
||||
-- @return type (string) chat type
|
||||
-- @return channel (string|number) channel number for whispers
|
||||
function Grichelde:CheckAndExtractMessageTypeTarget(text)
|
||||
self:DebugPrint("CheckAndExtractMessageTypeTarget : text:", text)
|
||||
|
||||
-- skip if no further text
|
||||
if nilOrEmpty(text) then
|
||||
return nil -- dont send text at all
|
||||
end
|
||||
|
||||
-- first word should be a chat command
|
||||
if sub(text, 1, 1) == "/" then
|
||||
-- extract chat command
|
||||
local chatCmd, targetAndText = self:SplitOnFirstMatch(text)
|
||||
assert(chatCmd ~= nil, "chatCmd is never nil")
|
||||
|
||||
local type = tFilter(self.SUPPORTED_CHAT_COMMANDS,
|
||||
function(_, k, _) return chatCmd == k end
|
||||
)
|
||||
self:DebugPrint("CheckAndExtractMessageTypeTarget : determined type:")
|
||||
self:DebugPrint(type)
|
||||
|
||||
if not tIsEmpty(type) then
|
||||
-- valid /chattype
|
||||
if type[1] == "WHISPER" then
|
||||
-- special reply handling
|
||||
if "/r" == chatCmd or "/reply" == chatCmd then
|
||||
-- reuse last type and target if possible
|
||||
local lastTold, lastToldType = ChatEdit_GetLastToldTarget()
|
||||
self:DebugPrint("CheckAndExtractMessageTypeTarget : lastTell, lastTellType =", lastTold, lastToldType)
|
||||
return targetAndText, lastToldType or type[1], lastTold
|
||||
elseif "/tt" == chatCmd then
|
||||
-- determine target from game world selection
|
||||
if (not UnitExists("target") or not UnitIsPlayer("target")) then
|
||||
self:ErrorPrint(self.L.Error_InvalidWhisperTarget)
|
||||
return nil -- dont send text at all
|
||||
end
|
||||
|
||||
local target = UnitName("target");
|
||||
if target == nil then
|
||||
self:ErrorPrint(self.L.Error_InvalidWhisperTarget)
|
||||
return nil -- dont send text at all
|
||||
end
|
||||
|
||||
-- eventually we found our target
|
||||
self:DebugPrint("CheckAndExtractMessageTypeTarget : target:", target)
|
||||
return targetAndText, type[1], target
|
||||
else
|
||||
-- determine target from text
|
||||
local target, msg = self:SplitOnFirstMatch(targetAndText)
|
||||
if target == nil then
|
||||
self:ErrorPrint(self.L.Error_InvalidWhisperTarget)
|
||||
return nil -- dont send text at all
|
||||
end
|
||||
|
||||
-- eventually we found our target
|
||||
self:DebugPrint("CheckAndExtractMessageTypeTarget : determined target:", target)
|
||||
return msg, type[1], target
|
||||
end
|
||||
else
|
||||
-- all other chat types
|
||||
return targetAndText, type[1], nil
|
||||
end
|
||||
else
|
||||
-- if not a valid chat command, try as a numbered channel
|
||||
local _, _, channelNumber = find(chatCmd, "^/(%d+)")
|
||||
if channelNumber ~= nil then
|
||||
local channelId = GetChannelName(channelNumber)
|
||||
if channelId ~= nil then
|
||||
return targetAndText, "CHANNEL", channelId
|
||||
end
|
||||
end
|
||||
|
||||
-- ignore any other slash commands
|
||||
self:ErrorPrint(self.L.Error_InvalidChannel)
|
||||
return nil -- dont send text at all
|
||||
end
|
||||
elseif IsOneBigEmote(self, text) then
|
||||
self:DebugPrint("CheckAndExtractMessageTypeTarget : determined EMOTE type")
|
||||
return text, "EMOTE", nil
|
||||
else
|
||||
-- in any other case, treat as ordinary text, assume default type and channel
|
||||
return text
|
||||
end
|
||||
end
|
||||
|
||||
function Grichelde:ConvertBlizzTypeToOption(channel)
|
||||
local option = tFilter(self.BLIZZ_TYPE_TO_OPTIONS,
|
||||
function(_, k, _) return channel == k end
|
||||
)
|
||||
if not tIsEmpty(option) then
|
||||
self:DebugPrint("ConvertBlizzTypeToOption : convert %s to %s", channel, option[1])
|
||||
return option[1]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Replaces all character occurrences for which replacements have been defined in the options,
|
||||
@@ -85,18 +277,9 @@ end
|
||||
-- @param text string
|
||||
-- @return string
|
||||
function Grichelde:ReplaceText(text)
|
||||
local lookAheads = {'|', '{', '%'}
|
||||
local finalText = ""
|
||||
local lookAheads = {'|', '{', '%', '*', '<', '(', 'o'}
|
||||
local newText = text
|
||||
|
||||
-- don't replace non-chat related slash commands
|
||||
local firstWord, line = self:SplitOnFirstMatch(text)
|
||||
if (firstWord ~= nil and tContains(self.slashCommands, firstWord)) then
|
||||
self:DebugPrint("ReplaceText : Found slash command:", firstWord )
|
||||
-- skip chat slash command
|
||||
finalText = finalText .. firstWord .. ' '
|
||||
newText = line
|
||||
end
|
||||
local finalText = ""
|
||||
|
||||
local current = 1
|
||||
local lastStart = 1
|
||||
@@ -108,8 +291,7 @@ function Grichelde:ReplaceText(text)
|
||||
if ( not tContains(lookAheads, currentChar)) then
|
||||
current = current + 1
|
||||
else
|
||||
|
||||
-- lookahead-check for itemLinks, textures and raid target icons
|
||||
-- lookahead-check for all preservable patterns (itemLinks, textures, emotes, ooc, etc.)
|
||||
local textAhead = sub(newText, current)
|
||||
local posEnd = self:CheckForPreversableText(textAhead)
|
||||
if posEnd > 0 then
|
||||
@@ -141,52 +323,64 @@ function Grichelde:ReplaceText(text)
|
||||
end
|
||||
|
||||
|
||||
--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures, raid target icons or
|
||||
--- %-substitutons and returns the end location of the match, or 0 if no pattern was found
|
||||
--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures, raid target icons,
|
||||
--- emotes, ooc or %-substitutons and returns the end location of the match, or 0 if no pattern was found
|
||||
-- @param text string
|
||||
-- @return number
|
||||
function Grichelde:CheckForPreversableText(text)
|
||||
self:DebugPrint("CheckForPreversableText : text:", text)
|
||||
self:TracePrint("CheckForPreversableText : text:", text)
|
||||
|
||||
-- do not replace these patterns
|
||||
local ignorePatterns = {
|
||||
"|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links)
|
||||
"|H.-|h", -- item links (http://www.wowwiki.com/ItemLink)
|
||||
"|T.-|t", -- textures
|
||||
"|K.-|k", -- Battle.net
|
||||
"|n", -- newline
|
||||
|
||||
"{rt[1-8]}", -- rumbered raid target icons
|
||||
"{Star}", -- named raid target icon 1
|
||||
"{Circle}", -- named raid target icon 2
|
||||
"{Coin}", -- named raid target icon 2
|
||||
"{Diamond}", -- named raid target icon 3
|
||||
"{Triangle}", -- named raid target icon 4
|
||||
"{Moon}", -- named raid target icon 5
|
||||
"{Square}", -- named raid target icon 6
|
||||
"{Cross}", -- named raid target icon 7
|
||||
"{X}", -- named raid target icon 7
|
||||
"{Skull}", -- named raid target icon 8
|
||||
|
||||
"%%n", -- player's name
|
||||
"%%z", -- player's currnt zone
|
||||
"%%sz", -- player's current sub-zone
|
||||
"%%loc", -- player's map coordinates
|
||||
"%%t", -- name of target
|
||||
"%%f", -- name of focus target
|
||||
"%%m", -- name of mouseover unit
|
||||
"%%p", -- name of player pet
|
||||
"%%tt" -- name of player's target's target
|
||||
}
|
||||
|
||||
-- Calling find on ever pattern might be inefficient but its way less code.
|
||||
for _, pattern in ipairs(ignorePatterns) do
|
||||
-- Calling find on ever pattern might be inefficient but its way less code than marching over every character
|
||||
for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS_CASE_SENSITIVE) do
|
||||
local pos1, pos2 = find(text, pattern)
|
||||
if pos1 == 1 and pos2 ~= nil then
|
||||
self:DebugPrint("CheckForPreversableText : Found ignore pattern \"%s\" at (%d, %d)", pattern, pos1, pos2)
|
||||
return pos2
|
||||
end
|
||||
end
|
||||
|
||||
-- emote detection
|
||||
for _, pattern in ipairs(Grichelde.EMOTE_PATTERNS) do
|
||||
local pos1, pos2 = find(text, pattern)
|
||||
if pos1 == 1 and pos2 ~= nil then
|
||||
local emote = sub(text, pos1, pos2)
|
||||
if (not self.db.profile.channels.emote) then
|
||||
self:DebugPrint("CheckForPreversableText : Found emote \"%s\" but preserved it", emote)
|
||||
return pos2
|
||||
else
|
||||
self:DebugPrint("CheckForPreversableText : Found emote \"%s\" at (%d, %d)", emote, pos1, pos2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- %-substitutions
|
||||
local lowerText = toLower(text)
|
||||
for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS_CASE_INSENSITIVE) do
|
||||
local pos1, pos2 = find(lowerText, pattern)
|
||||
if pos1 == 1 and pos2 ~= nil then
|
||||
self:DebugPrint("CheckForPreversableText : Found ignore pattern \"%s\" at (%d, %d)", pattern, pos1, pos2)
|
||||
return pos2
|
||||
end
|
||||
end
|
||||
|
||||
-- Localized raid target markers
|
||||
for _, localizedRT in ipairs(Grichelde.LOCALIZED_RAID_TARGETS) do
|
||||
local translation = toLower(self.L["IgnorePattern_" .. localizedRT])
|
||||
local locPattern = "{" .. translation .. "}"
|
||||
self:TracePrint("CheckForPreversableText : locPattern:", locPattern)
|
||||
local pos1, pos2 = find(lowerText, locPattern)
|
||||
if pos1 == 1 and pos2 ~= nil then
|
||||
self:DebugPrint("CheckForPreversableText : Found localized raid target marker \"%s\" at (%d, %d)", locPattern, pos1, pos2)
|
||||
return pos2
|
||||
end
|
||||
end
|
||||
|
||||
-- ooc: detection remaing text is treated as ooc completely!
|
||||
if sub(lowerText, 1, 4) == "ooc:" then
|
||||
self:DebugPrint("CheckForPreversableText : ooc for remaing text")
|
||||
return length(text)
|
||||
end
|
||||
|
||||
self:DebugPrint("CheckForPreversableText : no ignore pattern found")
|
||||
return 0
|
||||
end
|
||||
@@ -207,7 +401,7 @@ function Grichelde:ReplaceCharacters(text)
|
||||
local before = result
|
||||
local search = replTable.searchText
|
||||
|
||||
if not nilOrEmpty(search) then
|
||||
if not nilOrEmpty(search) and replTable.active then
|
||||
local replace = replTable.replaceText
|
||||
consolidate[replName] = {}
|
||||
|
||||
@@ -218,11 +412,11 @@ function Grichelde:ReplaceCharacters(text)
|
||||
local oldResult = result
|
||||
|
||||
local pos1, pos2 = find(oldResult, search, pos)
|
||||
self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2)
|
||||
while (pos1 and pos2 and pos1 <= pos2) do
|
||||
self:TracePrint("pos1: %d, pos2: %d", pos1, pos2)
|
||||
local pre = sub(result, 1, pos1 - 1 + offset)
|
||||
local post = sub(result, pos2 + 1 + offset)
|
||||
self:TracePrint("pre: %s, post: %s", pre, post)
|
||||
self:TracePrint("ReplaceCharacters : pre: %s, post: %s", pre, post)
|
||||
|
||||
-- actual replacement
|
||||
result = pre .. replace .. post
|
||||
@@ -233,9 +427,21 @@ function Grichelde:ReplaceCharacters(text)
|
||||
tInsert(consolidate[replName], pos1 + offset)
|
||||
end
|
||||
|
||||
-- update previous consolidate markers
|
||||
local diff = length(replace) - length(search)
|
||||
for key, posList in pairs(consolidate) do
|
||||
if key ~= replName then
|
||||
for i, pc in ipairs(posList) do
|
||||
if pos1 < pc then
|
||||
consolidate[key][i] = consolidate[key][i] + diff
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- replacement text can lengthen or shorten the resulting text
|
||||
-- after replacement result and lowerResult can have different sizes
|
||||
offset = offset + length(replace) - length(search)
|
||||
offset = offset + diff
|
||||
-- update values for next iteration
|
||||
pos = pos2 + 1
|
||||
pos1, pos2 = find(oldResult, search, pos)
|
||||
@@ -247,58 +453,70 @@ function Grichelde:ReplaceCharacters(text)
|
||||
local lowerSearch = toLower(search)
|
||||
|
||||
local pos1, pos2 = find(lowerResult, lowerSearch, pos)
|
||||
self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2)
|
||||
while (pos1 and pos2 and pos1 <= pos2) do
|
||||
self:TracePrint("pos1: %d, pos2: %d", pos1, pos2)
|
||||
local pre = sub(result, 1, pos1 - 1 + offset)
|
||||
local match = sub(result, pos1 + offset, pos2 + offset)
|
||||
local post = sub(result, pos2 + 1 + offset)
|
||||
self:TracePrint("pre: %s, match: %s, post: %s", pre, match, post)
|
||||
self:TracePrint("ReplaceCharacters : pre: %s, match: %s, post: %s", pre, match, post)
|
||||
|
||||
-- keep cases
|
||||
local utf8, uc, tc = nil, 0, 0
|
||||
local repl = ""
|
||||
local lastCase = nil
|
||||
for p = pos1, pos2 do
|
||||
self:TracePrint("p: %d", p)
|
||||
self:TracePrint("ReplaceCharacters : p: %d", p)
|
||||
local c = sub(match, p - pos1 + 1, p - pos1 + 1)
|
||||
local r = sub(replace, p - pos1 + 1, p - pos1 + 1) or ""
|
||||
|
||||
if (isUpper(c)) then -- all UPPER-CASE letter
|
||||
lastCase = true
|
||||
repl = repl .. toUpper(r)
|
||||
elseif (isLower(match)) then -- all lower_case letter
|
||||
lastCase = false
|
||||
repl = repl .. toLower(r)
|
||||
else -- no letter
|
||||
lastCase = nil
|
||||
repl = repl .. r
|
||||
-- put together umlaut or accent
|
||||
if utf8 ~= nil then
|
||||
c = utf8 .. c
|
||||
utf8 = nil
|
||||
end
|
||||
self:TracePrint("character: %s, %s", c, r)
|
||||
end
|
||||
|
||||
self:TracePrint("length %d > %d", length(replace), pos2 - pos1 + 1)
|
||||
if (length(replace) > pos2 - pos1 + 1) then
|
||||
local remainingReplace = sub(replace, pos2 - pos1 + 2)
|
||||
local nextLetter = sub(post, 1, 1)
|
||||
|
||||
self:TracePrint("rest: %s, n: %s, lastCase: %s", remainingReplace, nextLetter, lastCase)
|
||||
|
||||
if (isUpper(nextLetter)) then
|
||||
if lastCase == nil or lastCase == false then
|
||||
repl = repl .. remainingReplace
|
||||
else
|
||||
repl = repl .. toUpper(remainingReplace)
|
||||
-- if not umlaut or accent
|
||||
if c ~= "\195" then
|
||||
local r = sub(replace, p - pos1 + 1 - uc + tc, p - pos1 + 1 - uc + tc) or ""
|
||||
if r == "\195" then
|
||||
r = sub(replace, p - pos1 + 1 - uc + tc, p - pos1 + 1 - uc + tc + 1) or ""
|
||||
tc = tc + 1
|
||||
end
|
||||
elseif (isLower(nextLetter)) then
|
||||
if lastCase == nil or lastCase == true then
|
||||
repl = repl .. remainingReplace
|
||||
else
|
||||
repl = repl .. toLower(remainingReplace)
|
||||
self:TracePrint("ReplaceCharacters : character: %s, %s", c, r)
|
||||
|
||||
if (isUpper(c)) then -- UPPER-CASE letter
|
||||
lastCase = true
|
||||
repl = repl .. toUpper(r)
|
||||
elseif (isLower(c)) then -- lower_case letter
|
||||
lastCase = false
|
||||
repl = repl .. toLower(r)
|
||||
else -- no letter
|
||||
lastCase = nil
|
||||
repl = repl .. r
|
||||
end
|
||||
else
|
||||
-- no letter
|
||||
if lastCase == nil then
|
||||
-- handle UTF8 characters
|
||||
utf8 = c
|
||||
uc = uc + 1
|
||||
end
|
||||
end
|
||||
|
||||
self:TracePrint("ReplaceCharacters : length %d > %d", length(replace), pos2 - pos1 + 1 - uc + tc)
|
||||
if (length(replace) > pos2 - pos1 + 1 - uc + tc) then
|
||||
local remainingReplace = sub(replace, pos2 - pos1 + 2 - uc + tc)
|
||||
local nextLetter = sub(post, 1, 1)
|
||||
self:TracePrint("ReplaceCharacters : rest: %s, n: %s, lastCase: %s", remainingReplace, nextLetter, lastCase)
|
||||
|
||||
if lastCase == nil then
|
||||
if (isUpper(nextLetter)) then
|
||||
repl = repl .. toUpper(remainingReplace)
|
||||
elseif (isLower(nextLetter)) then
|
||||
repl = repl .. toLower(remainingReplace)
|
||||
else
|
||||
repl = repl .. remainingReplace
|
||||
elseif lastCase == false then
|
||||
end
|
||||
elseif lastCase == false then
|
||||
repl = repl .. toLower(remainingReplace)
|
||||
else
|
||||
if (isLower(nextLetter)) then
|
||||
repl = repl .. toLower(remainingReplace)
|
||||
else
|
||||
repl = repl .. toUpper(remainingReplace)
|
||||
@@ -308,16 +526,30 @@ function Grichelde:ReplaceCharacters(text)
|
||||
|
||||
-- actual replacement
|
||||
result = pre .. repl .. post
|
||||
self:DebugPrint("result: %s", result)
|
||||
self:DebugPrint("ReplaceCharacters : result: %s", result)
|
||||
|
||||
-- remember positions for consolidate
|
||||
if replTable.consolidate then
|
||||
tInsert(consolidate[replName], pos1 + offset)
|
||||
self:TracePrint("consolidate[" .. replName .. "] is:")
|
||||
self:TracePrint(consolidate[replName])
|
||||
end
|
||||
|
||||
-- update previous consolidate markers
|
||||
local diff = length(repl) - length(lowerSearch)
|
||||
for key, posList in pairs(consolidate) do
|
||||
if key ~= replName then
|
||||
for i, pc in ipairs(posList) do
|
||||
if pos1 < pc then
|
||||
consolidate[key][i] = consolidate[key][i] + diff
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- replacement text can be longer or shorter the resulting text
|
||||
-- after replacement result and lowerResult can have different sizes
|
||||
offset = offset + length(repl) - length(lowerSearch)
|
||||
offset = offset + diff
|
||||
-- update values for next iteration
|
||||
pos = pos2 + 1
|
||||
pos1, pos2 = find(lowerResult, lowerSearch, pos)
|
||||
@@ -327,18 +559,21 @@ function Grichelde:ReplaceCharacters(text)
|
||||
if before ~= result then
|
||||
self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\"", before, result)
|
||||
end
|
||||
if replTable.consolidate then
|
||||
self:DebugPrint("consolidate[" .. replName .. "] is:")
|
||||
self:DebugPrint(consolidate[replName])
|
||||
end
|
||||
else
|
||||
self:DebugPrint("ReplaceCharacters : Skip replacement for empty mapping")
|
||||
self:DebugPrint("ReplaceCharacters : Skip replacement for %s", replName)
|
||||
end
|
||||
end
|
||||
|
||||
-- consolidation is done last
|
||||
for replName, replTable in spairs(replacements) do
|
||||
local before = result
|
||||
--local search = replTable.searchText
|
||||
local search = replTable.searchText
|
||||
|
||||
if not nilOrEmpty(search) then
|
||||
if not nilOrEmpty(search) and replTable.active then
|
||||
local replace = replTable.replaceText
|
||||
local lowerResult = toLower(result)
|
||||
local offset = 0
|
||||
@@ -350,20 +585,20 @@ function Grichelde:ReplaceCharacters(text)
|
||||
|
||||
for _, pos1 in spairs(consolidate[replName]) do
|
||||
local pos2 = pos1 + length(replace) - 1
|
||||
self:TracePrint("pos1: %d, pos2: %d", pos1, pos2)
|
||||
local match = sub(lowerResult, pos1, pos2)
|
||||
self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2)
|
||||
local match = toLower(replace)
|
||||
local next = sub(lowerResult, pos2 + 1, pos2 + 1 + pos2 - pos1)
|
||||
self:TracePrint("match: %s, next: %s", match, next)
|
||||
self:TracePrint("ReplaceCharacters : match: %s, next: %s", match, next)
|
||||
|
||||
local _, p2 = find(next, "^" .. match)
|
||||
self:TracePrint("p2: %d", p2)
|
||||
self:TracePrint("ReplaceCharacters : p2: %d", p2)
|
||||
if (p2) then
|
||||
result = sub(result, 1, pos2 + offset) .. sub(result, pos2 + 1 + p2 + offset)
|
||||
|
||||
-- consolidation will shorten the resulting text
|
||||
offset = offset + length(result) - length(lowerResult)
|
||||
end
|
||||
self:DebugPrint("result: %s", result)
|
||||
self:DebugPrint("ReplaceCharacters : result: %s", result)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -371,7 +606,7 @@ function Grichelde:ReplaceCharacters(text)
|
||||
self:DebugPrint("ReplaceCharacters : consolidate \"%s\" with \"%s\"", before, result)
|
||||
end
|
||||
else
|
||||
self:DebugPrint("ReplaceCharacters : Skip consolidation for empty mapping")
|
||||
self:DebugPrint("ReplaceCharacters : Skip consolidation for %s", replName)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user