Version 1.1.0
- split of messages preserves item links, textures, substitutions and raid target markers - added safety measures to prevent endless replacement loops - bumped version for Shadowlands - bumped version for Naxxramas - split of messages with excessive length no longer causes errors or broken texts - proper handling of umlauts
This commit is contained in:
		
							
								
								
									
										3
									
								
								.retail.toc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.retail.toc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ## Interface: 90002 | ||||||
|  |  | ||||||
|  | ## X-Build: Retail | ||||||
							
								
								
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. | |||||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),  | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),  | ||||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||||
|  |  | ||||||
|  | ## Version 1.1.0 - 2020-12-08 | ||||||
|  | ### Added | ||||||
|  | - split of messages preserves item links, textures, substitutions and raid target markers | ||||||
|  | - added safety measures to prevent endless replacement loops | ||||||
|  | ### Changed | ||||||
|  | - bumped version for Shadowlands | ||||||
|  | - bumped version for Naxxramas | ||||||
|  | ### Fixed | ||||||
|  | - split of messages with excessive length no longer causes errors or broken texts | ||||||
|  | - proper handling of umlauts | ||||||
|  |  | ||||||
|  | ## Version 1.0.1 - 2020-10-17 | ||||||
|  | ### Changed | ||||||
|  | - bumped version for Shadowlands Pre-Patch | ||||||
|  |  | ||||||
| ## Version 1.0.0 - 2020-09-01 [First Release] | ## Version 1.0.0 - 2020-09-01 [First Release] | ||||||
| ### Added | ### Added | ||||||
| - info section with contact and thanks | - info section with contact and thanks | ||||||
| @@ -33,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||||||
| - stop replacements per mapping | - stop replacements per mapping | ||||||
| - more ooc recognition patterns | - more ooc recognition patterns | ||||||
| ### Fixed | ### Fixed | ||||||
| - keep cases of over-long replacements | - keep cases of replacements with excessive length | ||||||
|  |  | ||||||
| ## Version 0.8.0-beta - 2020-06-14 [Feature Complete] | ## Version 0.8.0-beta - 2020-06-14 [Feature Complete] | ||||||
| ### Added | ### Added | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| ## Interface: 11305 | ## Interface: 11306 | ||||||
|  |  | ||||||
| ## Title: Grichelde | ## Title: Grichelde | ||||||
| ## Notes: Replaces characters of your chat input line before sending. | ## Notes: Replaces characters of your chat input line before sending. | ||||||
| ## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile vor dem Versand. | ## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile vor dem Versenden. | ||||||
| ## Version: 1.0.0 | ## Version: 1.1.0 | ||||||
| ## Author: Teilzeit-Jedi | ## Author: Teilzeit-Jedi | ||||||
| ## eMail: tj@teilzeit-jedi.de | ## eMail: tj@teilzeit-jedi.de | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,44 +2,139 @@ | |||||||
| local _G = _G | local _G = _G | ||||||
| local Grichelde = _G.Grichelde or {} | local Grichelde = _G.Grichelde or {} | ||||||
|  |  | ||||||
| local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tConcat, tSize, tIsEmpty, find, sub, gsub, gmatch, getNextCharUtf8, isLetter, isUpper, isLower, toUpper, toLower, capitalize, trim, length, lengthUtf8 | local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tConcat, tSize, tIsEmpty, find, sub, gsub, gmatch, getNextCharUtf8, isLetter, isUpper, isLower, toUpper, toLower, capitalize, bytes2Char, trim, length, lengthUtf8 | ||||||
|     = Grichelde.F.IsAddOnLoaded, Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tFilter, Grichelde.F.tInsert, Grichelde.F.tConcat, Grichelde.F.tSize, Grichelde.F.tIsEmpty, |     = Grichelde.F.IsAddOnLoaded, Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tFilter, Grichelde.F.tInsert, Grichelde.F.tConcat, Grichelde.F.tSize, Grichelde.F.tIsEmpty, | ||||||
|       Grichelde.F.find, Grichelde.F.sub, Grichelde.F.gsub, Grichelde.F.gmatch, Grichelde.F.getNextCharUtf8, Grichelde.F.isLetter, Grichelde.F.isUpper, Grichelde.F.isLower, Grichelde.F.toUpper, Grichelde.F.toLower, Grichelde.F.capitalize, Grichelde.F.trim, Grichelde.F.length, Grichelde.F.lengthUtf8 |       Grichelde.F.find, Grichelde.F.sub, Grichelde.F.gsub, Grichelde.F.gmatch, Grichelde.F.getNextCharUtf8, Grichelde.F.isLetter, Grichelde.F.isUpper, Grichelde.F.isLower, Grichelde.F.toUpper, Grichelde.F.toLower, Grichelde.F.capitalize, Grichelde.F.bytes2Char, Grichelde.F.trim, Grichelde.F.length, Grichelde.F.lengthUtf8 | ||||||
|  |  | ||||||
| --- Splits a long text in longest possible chunks of <= 255 length, split at last available space | --- Splits a long text in longest possible chunks of <= 255 length, split at last available space | ||||||
| -- @param text string | -- @param text string | ||||||
| -- @return table | -- @return array of chunks | ||||||
| function Grichelde:SplitText(text) | function Grichelde:SplitText(text) | ||||||
|     local chunks = {} |     local chunks = {} | ||||||
|     local splitText = text |     local leftGuillemet = bytes2Char(194, 171) .. " " | ||||||
|     local textSize = length(splitText or "") |     local rightGuillemet = " " .. bytes2Char(194, 187) | ||||||
|  |     local chunkSize = Grichelde.INPUT_LIMIT - length(leftGuillemet) - length(rightGuillemet) | ||||||
|  |  | ||||||
|     while (textSize > 255) do |     local function preserveText(newText, chunk, blockText, posEnd) | ||||||
|         local chunk = sub(splitText, 1, 255) |         -- link found, block completed | ||||||
|         local remaining = "" |         self:TracePrint("SplitText : Found preservable text up to %s", posEnd) | ||||||
|  |         local preserved = sub(newText, 1, posEnd) | ||||||
|  |  | ||||||
|         -- special case: if space is the start of the next chunk, don't split this chunk |         if ((length(chunk) > 0) and (length(chunk .. blockText) > chunkSize)) then | ||||||
|         if (sub(splitText, 256, 256) ~= ' ') then |             -- block exceeds chunk, chunkify previous blocks | ||||||
|             -- split at last space, don't assign directly as nil might be returned |             self:DebugPrint("SplitText : add chunk:", chunk) | ||||||
|             local left, right = self:SplitOnLastMatch(chunk) |             tInsert(chunks, chunk .. rightGuillemet) | ||||||
|             if (left ~= nil) then |             chunk = leftGuillemet .. trim(blockText) | ||||||
|                 chunk = left |         else | ||||||
|  |             chunk = chunk .. blockText | ||||||
|         end |         end | ||||||
|             if (right ~= nil) then |  | ||||||
|                 remaining = right |         if ((length(chunk) > 0) and (length(chunk .. preserved) > chunkSize)) then | ||||||
|  |             -- block exceeds chunk, chunkify previous blocks | ||||||
|  |             self:DebugPrint("SplitText : add chunk:", chunk) | ||||||
|  |             tInsert(chunks, chunk .. rightGuillemet) | ||||||
|  |             chunk = leftGuillemet .. trim(preserved) | ||||||
|  |         else | ||||||
|  |             chunk = chunk .. preserved | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         blockText = "" | ||||||
|  |         newText = sub(newText, posEnd + 1) | ||||||
|  |  | ||||||
|  |         return newText, chunk, blockText, posEnd | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     if (length(text or "") <= Grichelde.INPUT_LIMIT) then | ||||||
|  |         self:DebugPrint("SplitText : no chunk:", text) | ||||||
|  |         tInsert(chunks, text) | ||||||
|  |     else | ||||||
|  |         local lookAheads = { '|', '*', '<', '%', '{', '(', 'o' } | ||||||
|  |  | ||||||
|  |         local newText = text or "" | ||||||
|  |         local chunk, blockText = "", "" | ||||||
|  |         local currentChar | ||||||
|  |         local escape = 0 | ||||||
|  |  | ||||||
|  |         -- must not enforce UTF-8 support here, as the positions are used | ||||||
|  |         while ((length(newText) > 0) and (escape < Grichelde.ENDLESS_LOOP_LIMIT)) do | ||||||
|  |             escape = escape + 1 | ||||||
|  |             local previousChar = currentChar | ||||||
|  |             local first, textAhead = getNextCharUtf8(newText) | ||||||
|  |             currentChar = first | ||||||
|  |             self:DebugPrint("SplitText : currentChar, escape: %s, %s", currentChar, escape) | ||||||
|  |             self:TracePrint("SplitText : chunk:", chunk) | ||||||
|  |             self:TracePrint("SplitText : newText:", newText) | ||||||
|  |  | ||||||
|  |             -- as there is not OR in Luas pattern matching, search for all of the exclude patterns after another is | ||||||
|  |             -- cumbersome and inefficient -> look for each char consecutively if it matches the starting pattern only | ||||||
|  |             -- and if if matches do full pattern matching | ||||||
|  |             if (currentChar == ' ') then | ||||||
|  |                 self:TracePrint("SplitText : block completed") | ||||||
|  |  | ||||||
|  |                 if ((length(chunk) > 0) and (length(chunk .. blockText) > chunkSize)) then | ||||||
|  |                     -- block exceeds chunk, chunkify previous blocks | ||||||
|  |                     self:DebugPrint("SplitText : add chunk:", chunk) | ||||||
|  |                     tInsert(chunks, chunk .. rightGuillemet) | ||||||
|  |                     chunk = leftGuillemet .. trim(blockText) | ||||||
|  |                 else | ||||||
|  |                     chunk = chunk .. blockText | ||||||
|  |                 end | ||||||
|  |  | ||||||
|  |                 blockText = currentChar | ||||||
|  |                 newText = textAhead | ||||||
|  |             elseif (tContains(lookAheads, currentChar)) then | ||||||
|  |                 -- lookahead-check for all preservable patterns (itemLinks, textures, emotes, ooc, etc.) | ||||||
|  |  | ||||||
|  |                 -- link detection | ||||||
|  |                 local linkPosEnd = self:CheckForLink(newText, currentChar) | ||||||
|  |                 if (linkPosEnd ~= nil) then | ||||||
|  |                     -- link found, block completed | ||||||
|  |                     newText, chunk, blockText = preserveText(newText, chunk, blockText, linkPosEnd) | ||||||
|  |                 else | ||||||
|  |                     -- substitution detection | ||||||
|  |                     local substPosEnd = self:CheckForSubstitutions(newText, currentChar) | ||||||
|  |                     if (substPosEnd ~= nil) then | ||||||
|  |                         -- substitution found, block completed | ||||||
|  |                         newText, chunk, blockText = preserveText(newText, chunk, blockText, substPosEnd) | ||||||
|  |                     else | ||||||
|  |                         -- raid target marker detection | ||||||
|  |                         local rtmPosEnd = self:CheckForRaidTargetMarkers(newText, currentChar) | ||||||
|  |                         if (rtmPosEnd ~= nil) then | ||||||
|  |                             -- raid target marker found, block completed | ||||||
|  |                             newText, chunk, blockText = preserveText(newText, chunk, blockText, rtmPosEnd) | ||||||
|  |                         else | ||||||
|  |                             blockText = blockText .. currentChar | ||||||
|  |                             newText = textAhead | ||||||
|  |                         end | ||||||
|  |                     end | ||||||
|  |                 end | ||||||
|  |             else | ||||||
|  |                 blockText = blockText .. currentChar | ||||||
|  |                 newText = textAhead | ||||||
|             end |             end | ||||||
|         end |         end | ||||||
|  |  | ||||||
|         self:DebugPrint("SplitText : chunk:", chunk) |         self:TracePrint("SplitText : main loop completed") | ||||||
|  |         if (length(chunk .. blockText) > 0) then | ||||||
|  |             -- catchup remaining text at the end | ||||||
|  |             if (length(chunk .. blockText) > chunkSize) then | ||||||
|  |                 -- block exceeds chunk, chunkify previous blocks | ||||||
|  |                 if (length(chunk) > 0) then | ||||||
|  |                     self:DebugPrint("SplitText : add chunk:", chunk) | ||||||
|  |                     tInsert(chunks, chunk .. rightGuillemet) | ||||||
|  |                     chunk = leftGuillemet .. trim(blockText) | ||||||
|  |                 else | ||||||
|  |                     chunk = chunk .. blockText | ||||||
|  |                 end | ||||||
|  |             else | ||||||
|  |                 chunk = chunk .. blockText | ||||||
|  |             end | ||||||
|  |  | ||||||
|  |             self:DebugPrint("SplitText : last chunk:", chunk) | ||||||
|  |             -- sub(chunk, 1, 255) can result in broken UTF8 chars and error message | ||||||
|             tInsert(chunks, chunk) |             tInsert(chunks, chunk) | ||||||
|         splitText = remaining .. sub(splitText, 256) |  | ||||||
|         textSize = length(splitText) |  | ||||||
|         end |         end | ||||||
|  |     end | ||||||
|     -- pickup remaining text < 255 |  | ||||||
|     self:DebugPrint("SplitText : last chunk:", splitText) |  | ||||||
|     tInsert(chunks, splitText) |  | ||||||
|  |  | ||||||
|     return chunks |     return chunks | ||||||
| end | end | ||||||
| @@ -63,10 +158,11 @@ function Grichelde:ReplaceCharacters(text, replName, replTable, consolidate, rep | |||||||
|         local ciPattern = "" |         local ciPattern = "" | ||||||
|         local ignored = {'^', '$', '(', ')', '.'} |         local ignored = {'^', '$', '(', ')', '.'} | ||||||
|         local quantifiers = {'*', '+', '-', '?'} |         local quantifiers = {'*', '+', '-', '?'} | ||||||
|         local pos = 1 |  | ||||||
|         local p, patRest = getNextCharUtf8(pattern) |         local p, patRest = getNextCharUtf8(pattern) | ||||||
|  |         local escape = 0 | ||||||
|  |  | ||||||
|         while (p ~= nil) do |         while ((p ~= nil) and (escape < Grichelde.ENDLESS_LOOP_LIMIT)) do | ||||||
|  |             escape = escape + 1 | ||||||
|             if (tContains(ignored, p) or tContains(quantifiers, p)) then |             if (tContains(ignored, p) or tContains(quantifiers, p)) then | ||||||
|                 -- ignore |                 -- ignore | ||||||
|                 ciPattern = ciPattern .. p |                 ciPattern = ciPattern .. p | ||||||
| @@ -429,47 +525,43 @@ function Grichelde:ReplaceAndConsolidate(text, replacements) | |||||||
|     return result |     return result | ||||||
| end | end | ||||||
|  |  | ||||||
| --- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures, raid target icons, | --- looks for colored items, item links or textures | ||||||
| --- emotes, ooc or %-substitutons and returns the end location of the match, or 0 if no pattern was found | function Grichelde:CheckForLink(text, currentChar) | ||||||
| -- @param text string |  | ||||||
| -- @param currentChar string(1) current character (first one) of the text, given for performance reasons |  | ||||||
| -- @param previousChar string(1) previous character of the text, otherwise unreachable |  | ||||||
| -- @param preserveEmotes boolean ignore replacements for emotes, for testing purposes |  | ||||||
| -- @return number |  | ||||||
| function Grichelde:CheckForPreversableText(text, currentChar, previousChar, replaceEmotes) |  | ||||||
|     self:DebugPrint("CheckForPreversableText : text:", text) |  | ||||||
|     local replaceEmotes = replaceEmotes or self.db.profile.channels.emote or false |  | ||||||
|  |  | ||||||
|     -- Calling find on ever pattern might be inefficient but its way less code than marching over every character |  | ||||||
|     if (currentChar == "|") then |     if (currentChar == "|") then | ||||||
|         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.LINKS) do |         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.LINKS) do | ||||||
|             local pos1, pos2 = find(text, "^" .. pattern) |             local pos1, pos2 = find(text, "^" .. pattern) | ||||||
|             if (pos1 == 1) and (pos2 ~= nil) then |             if (pos1 == 1) and (pos2 ~= nil) then | ||||||
|                 self:DebugPrint("CheckForPreversableText : Found link or texture pattern \"%s\" at (%d, %d)", pattern, pos1, pos2) |                 local match = sub(text, pos1, pos2) | ||||||
|  |                 self:DebugPrint("CheckForLink : Found link or texture pattern \"%s\" at (%d, %d)", pattern, pos1, pos2) | ||||||
|                 return pos2 |                 return pos2 | ||||||
|             end |             end | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|  |     return nil | ||||||
|  | end | ||||||
|  |  | ||||||
|     -- emote detection | --- looks for emotes | ||||||
|  | function Grichelde:CheckForEmote(text, currentChar, replaceEmotes) | ||||||
|     if (currentChar == "*" or currentChar == "<") then |     if (currentChar == "*" or currentChar == "<") then | ||||||
|         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.EMOTES) do |         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.EMOTES) do | ||||||
|             local pos1, pos2 = find(text, "^" .. pattern) |             local pos1, pos2 = find(text, "^" .. pattern) | ||||||
|             if (pos1 == 1) and (pos2 ~= nil) then |             if (pos1 == 1) and (pos2 ~= nil) then | ||||||
|                 local emote = sub(text, pos1, pos2) |                 local emote = sub(text, pos1, pos2) | ||||||
|                 if (not replaceEmotes) then |                 if (not replaceEmotes) then | ||||||
|                     self:DebugPrint("CheckForPreversableText : Found emote \"%s\" at (%d, %d), but preserved it", emote, pos1, pos2) |                     self:DebugPrint("CheckForEmote : Found emote \"%s\" at (%d, %d), but preserved it", emote, pos1, pos2) | ||||||
|                     return pos2 |                     return pos2 | ||||||
|                 else |                 else | ||||||
|                     self:DebugPrint("CheckForPreversableText : ignoring emote \"%s\" at (%d, %d)", emote, pos1, pos2) |                     self:DebugPrint("CheckForEmote : Processing emote \"%s\" at (%d, %d)", emote, pos1, pos2) | ||||||
|                 end |                 end | ||||||
|             end |             end | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|  |     return nil | ||||||
|  | end | ||||||
|  |  | ||||||
|  | --- looks for %-substitutions | ||||||
|  | function Grichelde:CheckForSubstitutions(text, currentChar) | ||||||
|     local lowerText = toLower(text) |     local lowerText = toLower(text) | ||||||
|  |  | ||||||
|     -- %-substitutions |  | ||||||
|     if (currentChar == "%") then |     if (currentChar == "%") then | ||||||
|         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.SUBSTITUTES) do |         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.SUBSTITUTES) do | ||||||
|             local pos1, pos2 = find(lowerText, "^" .. pattern) |             local pos1, pos2 = find(lowerText, "^" .. pattern) | ||||||
| @@ -479,8 +571,12 @@ function Grichelde:CheckForPreversableText(text, currentChar, previousChar, repl | |||||||
|             end |             end | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|  |     return nil | ||||||
|  | end | ||||||
|  |  | ||||||
|     -- raid target markers | --- looks for general and localized raid target markers | ||||||
|  | function Grichelde:CheckForRaidTargetMarkers(text, currentChar) | ||||||
|  |     local lowerText = toLower(text) | ||||||
|     if (currentChar == "{") then |     if (currentChar == "{") then | ||||||
|         -- rt1-9 |         -- rt1-9 | ||||||
|         local pattern = Grichelde.IGNORE_PATTERNS.RAID_TARGETS[1] |         local pattern = Grichelde.IGNORE_PATTERNS.RAID_TARGETS[1] | ||||||
| @@ -501,8 +597,12 @@ function Grichelde:CheckForPreversableText(text, currentChar, previousChar, repl | |||||||
|             end |             end | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|  |     return nil | ||||||
|  | end | ||||||
|  |  | ||||||
|     -- ooc bracket detection | --- looks for ooc with brackets | ||||||
|  | function Grichelde:CheckForOocBrackets(text, currentChar) | ||||||
|  |     local lowerText = toLower(text) | ||||||
|     if (currentChar == "(") then |     if (currentChar == "(") then | ||||||
|         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.OOC_BRACKETS) do |         for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.OOC_BRACKETS) do | ||||||
|             local pos1, pos2 = find(lowerText, "^" .. pattern) |             local pos1, pos2 = find(lowerText, "^" .. pattern) | ||||||
| @@ -512,15 +612,63 @@ function Grichelde:CheckForPreversableText(text, currentChar, previousChar, repl | |||||||
|             end |             end | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|  |     return nil | ||||||
|  | end | ||||||
|  |  | ||||||
|     -- ooc without brackets: remaing text is treated as ooc completely! | --- looks for ooc without brackets | ||||||
|  | function Grichelde:CheckForOocNoBrackets(text, currentChar, previousChar) | ||||||
|  |     local lowerText = toLower(text) | ||||||
|     if (currentChar == "o") then |     if (currentChar == "o") then | ||||||
|         local pattern = Grichelde.IGNORE_PATTERNS.OOC_NO_BRACKETS[1] |         local pattern = Grichelde.IGNORE_PATTERNS.OOC_NO_BRACKETS[1] | ||||||
|         if ((previousChar == nil) or (find(previousChar, "%s") ~= nil)) and (find(lowerText, pattern) ~= nil) then |         if ((previousChar == nil) or (find(previousChar, "%s") ~= nil)) and (find(lowerText, pattern) ~= nil) then | ||||||
|             self:DebugPrint("CheckForPreversableText : ooc for remaing text") |             self:DebugPrint("CheckForPreversableText : ooc for remaing text") | ||||||
|  |             -- remaing text is treated as ooc completely! | ||||||
|             return length(text) |             return length(text) | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|  |     return nil | ||||||
|  | end | ||||||
|  |  | ||||||
|  | --- 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 | ||||||
|  | -- @param currentChar string(1) current character (first one) of the text, given for performance reasons | ||||||
|  | -- @param previousChar string(1) previous character of the text, otherwise unreachable | ||||||
|  | -- @param preserveEmotes boolean ignore replacements for emotes, for testing purposes | ||||||
|  | -- @return number | ||||||
|  | function Grichelde:CheckForPreversableText(text, currentChar, previousChar, replaceEmotes) | ||||||
|  |     self:DebugPrint("CheckForPreversableText : text:", text) | ||||||
|  |     local replaceEmotes = replaceEmotes or self.db.profile.channels.emote or false | ||||||
|  |  | ||||||
|  |     local linkPos = self:CheckForLink(text, currentChar) | ||||||
|  |     if (linkPos ~= nil) then | ||||||
|  |         return linkPos | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local emotePos = self:CheckForEmote(text, currentChar, replaceEmotes) | ||||||
|  |     if (emotePos ~= nil) then | ||||||
|  |         return emotePos | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local substPos = self:CheckForSubstitutions(text, currentChar) | ||||||
|  |     if (substPos ~= nil) then | ||||||
|  |         return substPos | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local rtmPos = self:CheckForRaidTargetMarkers(text, currentChar) | ||||||
|  |     if (rtmPos ~= nil) then | ||||||
|  |         return rtmPos | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local oocBracketPos = self:CheckForOocBrackets(text, currentChar) | ||||||
|  |     if (oocBracketPos ~= nil) then | ||||||
|  |         return oocBracketPos | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local oocNoBracketPos = self:CheckForOocNoBrackets(text, currentChar, previousChar) | ||||||
|  |     if (oocNoBracketPos ~= nil) then | ||||||
|  |         return oocNoBracketPos | ||||||
|  |     end | ||||||
|  |  | ||||||
|     self:DebugPrint("CheckForPreversableText : no ignore pattern found") |     self:DebugPrint("CheckForPreversableText : no ignore pattern found") | ||||||
|     return 0 |     return 0 | ||||||
| @@ -536,49 +684,49 @@ function Grichelde:ReplaceText(text, replacements, replaceEmotes) | |||||||
|     local newText = text |     local newText = text | ||||||
|     local preserveEmotes = replaceEmotes or self.db.profile.channels.emote or false |     local preserveEmotes = replaceEmotes or self.db.profile.channels.emote or false | ||||||
|     local replacements = replacements or self.db.profile.replacements or {} |     local replacements = replacements or self.db.profile.replacements or {} | ||||||
|     local finalText = "" |     local finalText, replaceText = "", "" | ||||||
|  |     local currentChar | ||||||
|  |     local escape = 0 | ||||||
|  |  | ||||||
|     local currentChar, previousChar |     -- must not enforce UTF-8 support here, as the positions are used | ||||||
|     local current = 1 |     while ((length(newText) > 0) and (escape < Grichelde.ENDLESS_LOOP_LIMIT)) do | ||||||
|     local lastStart = 1 |         escape = escape + 1 | ||||||
|  |         local previousChar = currentChar | ||||||
|     -- no UTF-8 support required here, as the positions are used |         local first, textAhead = getNextCharUtf8(newText) | ||||||
|     while current <= length(newText) do |         currentChar = first | ||||||
|         previousChar = currentChar |         self:TracePrint("ReplaceText : currentChar : %s", currentChar) | ||||||
|         currentChar = sub(newText, current, current) |  | ||||||
|         self:TracePrint("ReplaceText : current/char : %s,%s", current, currentChar) |  | ||||||
|  |  | ||||||
|         -- as there is not OR in Luas pattern matching, search for all of the exclude patterns after another is |         -- as there is not OR in Luas pattern matching, search for all of the exclude patterns after another is | ||||||
|         -- cumbersome and inefficient -> look for each char consecutively if it matches the starting pattern only |         -- cumbersome and inefficient -> look for each char consecutively if it matches the starting pattern only | ||||||
|         -- and if if matches do full pattern matching |         -- and if if matches do full pattern matching | ||||||
|         if (not tContains(lookAheads, currentChar)) then |         if (tContains(lookAheads, currentChar)) then | ||||||
|             current = current + 1 |  | ||||||
|         else |  | ||||||
|             -- lookahead-check for all preservable patterns (itemLinks, textures, emotes, ooc, etc.) |             -- lookahead-check for all preservable patterns (itemLinks, textures, emotes, ooc, etc.) | ||||||
|             local textAhead = sub(newText, current) |             --local textAhead = sub(newText, current) | ||||||
|             local posEnd = self:CheckForPreversableText(textAhead, currentChar, previousChar, preserveEmotes) |             local posEnd = self:CheckForPreversableText(newText, currentChar, previousChar, preserveEmotes) | ||||||
|             if (posEnd > 0) then |             if (posEnd > 0) then | ||||||
|                 self:DebugPrint("ReplaceText : Found an ignore pattern") |                 self:DebugPrint("ReplaceText : Found an ignore pattern") | ||||||
|  |  | ||||||
|                 -- split text and continue after preserved text |                 -- replace all text up until now | ||||||
|                 local textBefore = sub(newText, lastStart, current - 1) |                 local replacement = self:ReplaceAndConsolidate(replaceText, replacements) | ||||||
|                 local replacement = self:ReplaceAndConsolidate(textBefore, replacements) |                 local preserved = sub(newText, 1, posEnd) | ||||||
|                 local preservedText = sub(textAhead, 1, posEnd) |  | ||||||
|  |  | ||||||
|                 finalText = finalText .. replacement .. preservedText |                 finalText = finalText .. replacement .. preserved | ||||||
|                 current = current + posEnd |                 replaceText = "" | ||||||
|                 lastStart = current |                 newText = sub(newText, posEnd + 1) | ||||||
|                 self:DebugPrint("ReplaceText : restarting at", lastStart) |                 self:DebugPrint("ReplaceText : remaining text", newText) | ||||||
|             else |             else | ||||||
|                 -- no corresponding end was found to start pattern, continue loop with next char |                 -- no corresponding end was found to start pattern, continue loop with next char | ||||||
|                 current = current + 1 |                 replaceText = replaceText .. currentChar | ||||||
|  |                 newText = textAhead | ||||||
|             end |             end | ||||||
|  |         else | ||||||
|  |             replaceText = replaceText .. currentChar | ||||||
|  |             newText = textAhead | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     -- catchup remaining text to the end |     -- catchup remaining text to the end | ||||||
|     local remainingText = sub(newText, lastStart) |     local replacement = self:ReplaceAndConsolidate(replaceText, replacements) | ||||||
|     local replacement = self:ReplaceAndConsolidate(remainingText, replacements) |  | ||||||
|     finalText = finalText .. replacement |     finalText = finalText .. replacement | ||||||
|  |  | ||||||
|     self:DebugPrint("ReplaceText : replaced \"%s\"", text) |     self:DebugPrint("ReplaceText : replaced \"%s\"", text) | ||||||
| @@ -592,22 +740,24 @@ function Grichelde:IsOneBigEmote(text) | |||||||
|  |  | ||||||
|     -- emote detection |     -- emote detection | ||||||
|     local isEmote = false |     local isEmote = false | ||||||
|     -- scheme *emote* |     local firstChar, rest = getNextCharUtf8(firstWord) | ||||||
|     if (sub(firstWord, 1, 1) == "<") then |  | ||||||
|  |     -- scheme <emote> | ||||||
|  |     if (firstChar == "<") then | ||||||
|         -- search for emote end |         -- search for emote end | ||||||
|         local _, emoteEnd = find(text, "%>", 2) |         local _, emoteEnd = find(text, "%>", 2) | ||||||
|         isEmote = (emoteEnd == length(text)) |         isEmote = (emoteEnd == lengthUtf8(text)) | ||||||
|     end |  | ||||||
|     if (not isEmote and (sub(firstWord, 1, 1) == "*")) then |  | ||||||
|         -- search for emote end |  | ||||||
|         local _, emoteEnd = find(text, "%*", 2) |  | ||||||
|         isEmote = (emoteEnd == length(text)) |  | ||||||
|     end |     end | ||||||
|  |     if (not isEmote and (firstChar == "*")) then | ||||||
|  |         if (getNextCharUtf8(rest) == "*") then | ||||||
|             -- scheme **emote** |             -- scheme **emote** | ||||||
|     if (not isEmote and (sub(firstWord, 1, 2) == "**")) then |  | ||||||
|         -- search for emote end |  | ||||||
|             local _, emoteEnd = find(text, "%*%*", 3) |             local _, emoteEnd = find(text, "%*%*", 3) | ||||||
|         isEmote = (emoteEnd == length(text)) |             isEmote = (emoteEnd == lengthUtf8(text)) | ||||||
|  |         else | ||||||
|  |             -- scheme *emote* | ||||||
|  |             local _, emoteEnd = find(text, "%*", 2) | ||||||
|  |             isEmote = (emoteEnd == lengthUtf8(text)) | ||||||
|  |         end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     -- the whole text is one big emote |     -- the whole text is one big emote | ||||||
| @@ -659,7 +809,7 @@ function Grichelde:CheckReplacementAllowed(text, channel) | |||||||
|     assert(firstWord ~= nil, "firstWord is never nil") |     assert(firstWord ~= nil, "firstWord is never nil") | ||||||
|  |  | ||||||
|     -- don't replace slash commands |     -- don't replace slash commands | ||||||
|     if (sub(firstWord, 1, 1) == "/") then |     if (getNextCharUtf8(firstWord) == "/") then | ||||||
|         self:DebugPrint("CheckReplacementAllowed : skip other slash commands:", firstWord) |         self:DebugPrint("CheckReplacementAllowed : skip other slash commands:", firstWord) | ||||||
|         return false |         return false | ||||||
|     end |     end | ||||||
| @@ -691,7 +841,7 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message) | |||||||
|     end |     end | ||||||
|  |  | ||||||
|     -- first word should be a chat command |     -- first word should be a chat command | ||||||
|     if (sub(message, 1, 1) == "/") then |     if (getNextCharUtf8(message) == "/") then | ||||||
|         -- extract chat command |         -- extract chat command | ||||||
|         local chatCmd, targetAndText = self:SplitOnFirstMatch(message) |         local chatCmd, targetAndText = self:SplitOnFirstMatch(message) | ||||||
|         assert(chatCmd ~= nil, "chatCmd is never nil") |         assert(chatCmd ~= nil, "chatCmd is never nil") | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ local Grichelde = _G.Grichelde or {} | |||||||
| -- constants and upvalues | -- constants and upvalues | ||||||
| Grichelde.LOG_LEVEL = { DEBUG = 1, TRACE = 2 } | Grichelde.LOG_LEVEL = { DEBUG = 1, TRACE = 2 } | ||||||
|  |  | ||||||
|  | Grichelde.INPUT_LIMIT = 255 | ||||||
|  | Grichelde.ENDLESS_LOOP_LIMIT = 10000 | ||||||
| Grichelde.MAPPING_OFFSET = 10 | Grichelde.MAPPING_OFFSET = 10 | ||||||
| Grichelde.MINIMAP_ENABLED = 1.0 | Grichelde.MINIMAP_ENABLED = 1.0 | ||||||
| Grichelde.MINIMAP_DARKENDED = 0.5 | Grichelde.MINIMAP_DARKENDED = 0.5 | ||||||
| @@ -116,6 +118,7 @@ Grichelde.BLIZZ_TYPE_TO_OPTIONS = { | |||||||
| } | } | ||||||
|  |  | ||||||
| -- do not replace these patterns | -- do not replace these patterns | ||||||
|  | -- combined item links in the chat will look like this: |cff9d9d9d|Hitem:3299::::::::20:257::::::|h[Fractured Canine]|h|r | ||||||
| Grichelde.IGNORE_PATTERNS = { | Grichelde.IGNORE_PATTERNS = { | ||||||
|     LINKS = { |     LINKS = { | ||||||
|         "|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links) |         "|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user