Download CommonLua/Core/locutils.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 25 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Core/locutils.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Core/locutils.lua
-
curl -L -o locutils.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Core/locutils.lua
25 kB
| if FirstLoad then | |
| g_LipSyncData = {} | |
| end | |
| --- | |
| --- Gets the filename for a voice sample based on the given translation ID. | |
| --- | |
| --- @param T table The translation object containing the ID. | |
| --- @param seed number An optional seed value for randomizing the voice variation. | |
| --- @return string The filename of the voice sample. | |
| --- | |
| function GetVoiceFilename(T, seed) | |
| if not T then | |
| return | |
| end | |
| local id = TGetID(T) | |
| if not id then | |
| return | |
| end | |
| if g_VoiceVariations then | |
| local all_vars = g_VoiceVariations[id] or g_VoiceVariations[tostring(id)] | |
| if all_vars and #all_vars > 0 then | |
| local var = 1 + (seed and BraidRandom(seed, #all_vars) or AsyncRand(#all_vars)) | |
| return all_vars[var] | |
| end | |
| end | |
| local not_robot_voice = "CurrentLanguage/Voices/" .. id | |
| -- if not (Platform.developer and Platform.pc and config.RunUnpacked) then | |
| if not config.VoicesTTS or not GetAccountStorageOptionValue("VoicesTTS") then | |
| return not_robot_voice | |
| end | |
| if GetSoundDuration(not_robot_voice) then | |
| return not_robot_voice | |
| end | |
| local robo_voice = "CurrentLanguage/VoicesTTS/" .. id | |
| if io.exists(robo_voice .. ".opus") then | |
| return robo_voice | |
| end | |
| end | |
| -- choose the sound by the translation id of the given text | |
| --- | |
| --- Gets the filename for a voice sample based on the given translation ID and actor. | |
| --- | |
| --- @param T table The translation object containing the ID. | |
| --- @param actor string The actor for the voice sample (e.g. "male", "female"). | |
| --- @param pathName string An optional path name for the voice sample. | |
| --- @return string The filename of the voice sample. | |
| --- | |
| function VoiceSampleByText(T, actor, pathName) | |
| local id = TGetID(T) | |
| if not id then | |
| return false | |
| end | |
| local sample | |
| if actor == "male" then | |
| sample = pathName or "CurrentLanguage/Voices/Male/" .. id | |
| elseif actor == "female" then | |
| sample = pathName or "CurrentLanguage/Voices/Female/" .. id | |
| elseif actor and actor ~= "" and actor ~= "narrator" then | |
| sample = string.concat(pathName or "CurrentLanguage/Voices/", not pathName and actor or "", "/", id) | |
| else | |
| sample = pathName or "CurrentLanguage/Voices/" .. id | |
| end | |
| -- so we can play robo_voices in conversations | |
| if not io.exists(sample .. ".opus") then | |
| sample = GetVoiceFilename(T) | |
| end | |
| return sample | |
| end | |
| --- | |
| --- Generates a voice context string based on the provided field, annotation, voiced property, and annotation property. | |
| --- | |
| --- @param field string|table The field(s) to check for voice data. | |
| --- @param annotation string An optional annotation to include in the voice context. | |
| --- @param voiced_prop string An optional property to check if the object has voice data. | |
| --- @param annotation_prop string An optional property to check for additional annotation data. | |
| --- @return function A function that generates the voice context string for a given object. | |
| --- | |
| function VoicedContextFromField(field, annotation, voiced_prop, annotation_prop) | |
| return function(obj, prop_meta, parent) | |
| if type(field) == "table" then | |
| for i = 1, #field do | |
| if obj[field[i]] and obj[field[i]] ~= "" then | |
| field = field[i] | |
| break | |
| end | |
| end | |
| end | |
| local extra_annotation = annotation_prop and obj[annotation_prop] | |
| if IsT(extra_annotation) then | |
| extra_annotation = _InternalTranslate(extra_annotation) | |
| end | |
| if (not voiced_prop or obj[voiced_prop]) and obj[field] then | |
| if annotation and extra_annotation then | |
| return string.format("%s %s voice:%s", annotation, extra_annotation, obj[field]) | |
| elseif annotation or extra_annotation then | |
| return string.format("%s voice:%s", annotation or extra_annotation, obj[field]) | |
| else | |
| return string.format("voice:%s", obj[field]) | |
| end | |
| else | |
| return annotation and extra_annotation and (annotation .. " " .. extra_annotation) or annotation | |
| or extra_annotation or "" | |
| end | |
| end | |
| end | |
| --- | |
| --- Generates localization IDs for the properties of the given object. | |
| --- | |
| --- @param obj table The object to generate localization IDs for. | |
| --- @return table The modified object with localization IDs. | |
| --- | |
| function GenerateLocalizationIDs(obj) | |
| if not obj then | |
| return | |
| end | |
| if PropObjHasMember(obj, "GetProperties") then | |
| local props = obj:GetProperties() | |
| for i = 1, #props do | |
| local prop = props[i] | |
| local id = prop.id | |
| if prop.translate then | |
| local value = obj:GetProperty(id) | |
| if IsT(value) and not obj:IsDefaultPropertyValue(id, prop, value) then | |
| assert(not THasArgs(value)) | |
| obj:SetProperty(id, T {RandomLocId(), TDevModeGetEnglishText(value)}) | |
| end | |
| end | |
| local editor = prop.editor | |
| if editor == "T_list" and not obj:IsDefaultPropertyValue(id, prop, value) then | |
| local tlist = obj:GetProperty(id) | |
| for idx, text in ipairs(tlist) do | |
| tlist[idx] = T {RandomLocId(), TDevModeGetEnglishText(text)} | |
| end | |
| elseif editor == "nested_obj" then | |
| GenerateLocalizationIDs(obj:GetProperty(id)) | |
| elseif editor == "nested_list" then | |
| for _, subobj in ipairs(obj:GetProperty(id)) do | |
| GenerateLocalizationIDs(subobj) | |
| end | |
| end | |
| end | |
| end | |
| for i = 1, #obj do | |
| GenerateLocalizationIDs(obj[i], true) | |
| end | |
| return obj | |
| end | |
| g_TestUIPlatform = false -- win32 uI | |
| -- g_TestUIPlatform = "ps4" -- to test ps4 UI interface | |
| -- g_TestUIPlatform = "ps5" -- to test ps5 UI interface | |
| -- g_TestUIPlatform = "xbox" -- to test xbox UI interface | |
| -- g_TestUIPlatform = "xbox_one" -- to test xbox one UI interface | |
| -- g_TestUIPlatform = "xbox_series" -- to test xbox series UI interface | |
| -- g_TestUIPlatform = "switch" -- to test Switch UI interface | |
| --- button images remap for PlayStation | |
| --- A mapping of Xbox button names to their PlayStation equivalents. | |
| --- | |
| --- This table is used to translate button names between the Xbox and PlayStation | |
| --- controller layouts. The keys in this table are the Xbox button names, and the | |
| --- values are the corresponding PlayStation button names. | |
| --- | |
| --- For example, the Xbox "ButtonA" button maps to the PlayStation "Circle" button | |
| --- (or "Cross" button if `const.PlayStationEnterBtnCircle` is false). | |
| --- | |
| --- @field ButtonA string The PlayStation button that corresponds to the Xbox "ButtonA" button. | |
| --- @field ButtonB string The PlayStation button that corresponds to the Xbox "ButtonB" button. | |
| --- @field ButtonY string The PlayStation button that corresponds to the Xbox "ButtonY" button. | |
| --- @field ButtonX string The PlayStation button that corresponds to the Xbox "ButtonX" button. | |
| --- @field LT string The PlayStation button that corresponds to the Xbox "LT" button. | |
| --- @field RT string The PlayStation button that corresponds to the Xbox "RT" button. | |
| --- @field LS string The PlayStation button that corresponds to the Xbox "LS" button. | |
| --- @field RS string The PlayStation button that corresponds to the Xbox "RS" button. | |
| --- @field LSPress string The PlayStation button that corresponds to the Xbox "LSPress" button. | |
| --- @field RSPress string The PlayStation button that corresponds to the Xbox "RSPress" button. | |
| --- @field LB string The PlayStation button that corresponds to the Xbox "LB" button. | |
| --- @field RB string The PlayStation button that corresponds to the Xbox "RB" button. | |
| --- @field Start string The PlayStation button that corresponds to the Xbox "Start" button. | |
| --- @field Back string The PlayStation button that corresponds to the Xbox "Back" button. | |
| const.XboxToPlayStationButtons = {ButtonA=const.PlayStationEnterBtnCircle and "Circle" or "Cross", | |
| ButtonB=const.PlayStationEnterBtnCircle and "Cross" or "Circle", ButtonY="Triangle", ButtonX="Square", LT="L2", | |
| RT="R2", LS="L", RS="R", LSPress="L3", RSPress="R3", LB="L1", RB="R1", Start="Options", Back="TouchPad"} | |
| const.ShortenedButtonNames = { | |
| LeftTrigger = "LT", | |
| RightTrigger = "RT", | |
| LeftShoulder = "LB", | |
| RightShoulder = "RB", | |
| RightThumbClick = "RSPress", | |
| LeftThumbClick = "LSPress", | |
| } | |
| local TagToImageName = { | |
| ["ButtonA"] = "ButtonA", | |
| ["ButtonB"] = "ButtonB", | |
| ["ButtonX"] = "ButtonX", | |
| ["ButtonY"] = "ButtonY", | |
| ["DPad"] = "DPad", | |
| ["DPadUp"] = "DPadUp", | |
| ["DPadDown"] = "DPadDown", | |
| ["DPadLeft"] = "DPadLeft", | |
| ["DPadRight"] = "DPadRight", | |
| ["DPadUpDown"] = "DPad_Up_Down", | |
| ["DPadLeftRight"] = "DPad_Left_Right", | |
| ["LT"] = "LT", | |
| ["RT"] = "RT", | |
| ["LeftTrigger"] = "LT", | |
| ["RightTrigger"] = "RT", | |
| ["LS"] = "LS", | |
| ["RS"] = "RS", | |
| ["LSPress"] = "LSPress", | |
| ["RSPress"] = "RSPress", | |
| ["LB"] = "LB", | |
| ["RB"] = "RB", | |
| ["Start"] = "Start", | |
| ["Back"] = "Back", | |
| ["lsupdown"] = "ls_up_down", | |
| ["lsright"] = "lsright", | |
| ["lsleft"] = "lsleft", | |
| ["lsup"] = "lsup", | |
| ["lsdown"] = "lsdown", | |
| ["rsupdown"] = "rs_up_down", | |
| ["rsright"] = "rsright", | |
| ["rsleft"] = "rsleft", | |
| ["rsup"] = "rsup", | |
| ["rsdown"] = "rsdown", | |
| ["RightThumbUp"] = "rsup", | |
| ["RightThumbDown"] = "rsdown", | |
| ["Controller"] = "Controller", | |
| } | |
| -- these get auto-replaced in translated text, e.g. <A> is the same as <image UI/Xbox/A.tga> | |
| --- | |
| --- Recreates the lookup table that maps button tags to their corresponding platform-specific image names. | |
| --- This function is called when the active controller type changes, to ensure the correct button images are displayed. | |
| --- | |
| --- @function RecreateButtonsTagLookupTable | |
| --- @return nil | |
| function RecreateButtonsTagLookupTable() | |
| for tag, img in pairs(TagToImageName) do | |
| const.TagLookupTable[tag] = GetPlatformSpecificImageTag(img) | |
| end | |
| end | |
| --- | |
| --- Handles updating the active controller type for the PC platform. | |
| --- This function is called when the XInput controller is initialized or the active controller is updated. | |
| --- It updates the `g_PCActiveControllerType` global variable and triggers the `OnControllerTypeChanged` message if the controller type has changed. | |
| --- | |
| --- @function UpdateActiveControllerType | |
| --- @return nil | |
| function UpdateActiveControllerType() | |
| end | |
| --- | |
| --- Returns the current active controller type for the PC platform. | |
| --- | |
| --- @function GetPCActiveControllerType | |
| --- @return string|boolean the active controller type, or `false` if no controller is connected | |
| function GetPCActiveControllerType() | |
| end | |
| if Platform.pc then | |
| function UpdateActiveControllerType() | |
| if not rawget(_G, "XInput") or not IsXInputControllerConnected() or not ActiveController then | |
| return | |
| end | |
| local previous = g_PCActiveControllerType | |
| g_PCActiveControllerType = XInput.GetControllerType(ActiveController) or false | |
| if g_PCActiveControllerType ~= previous then | |
| RecreateButtonsTagLookupTable() | |
| Msg("OnControllerTypeChanged", g_PCActiveControllerType) | |
| end | |
| end | |
| OnMsg.XInputInitialized = UpdateActiveControllerType | |
| OnMsg.ActiveControllerUpdated = UpdateActiveControllerType | |
| if FirstLoad then | |
| g_PCActiveControllerType = false | |
| end | |
| function GetPCActiveControllerType() | |
| return g_PCActiveControllerType | |
| end | |
| else | |
| function GetPCActiveControllerType() | |
| end | |
| end | |
| function ShouldShowPS4Images() | |
| return Platform.ps4 or g_TestUIPlatform == "ps4" or (not g_TestUIPlatform and GetPCActiveControllerType() == "ps4") | |
| end | |
| function ShouldShowPS5Images() | |
| return Platform.ps5 or g_TestUIPlatform == "ps5" or (not g_TestUIPlatform and GetPCActiveControllerType() == "ps5") | |
| end | |
| --- | |
| --- Returns the platform-specific image name for the given button. | |
| --- | |
| --- @param btn string the button name | |
| --- @param platform string the platform name | |
| --- @return string the platform-specific image name | |
| function GetPlatformSpecificImageName(btn, platform) | |
| btn = const.ShortenedButtonNames[btn] or btn | |
| local path = "UI/DesktopGamepad/" | |
| local ext = ".tga" | |
| local btnimg = btn | |
| if platform == "ps4" then | |
| path = "UI/PS4/" | |
| btnimg = (const.XboxToPlayStationButtons[btn] or btn) | |
| elseif platform == "ps5" then | |
| path = "UI/PS5/" | |
| btnimg = (const.XboxToPlayStationButtons[btn] or btn) | |
| elseif platform == "xbox_one" then | |
| path = "UI/XboxOne/" | |
| elseif platform == "xbox_series" then | |
| path = "UI/XboxSeries/" | |
| elseif platform == "switch" then | |
| path = "UI/Switch/" | |
| else -- win32 | |
| end | |
| return path .. btnimg .. ext | |
| end | |
| --- | |
| --- Collects the resource IDs for all platform-specific button images used in the game. | |
| --- | |
| --- @param resIds table a table to store the resource IDs and their associated DLC | |
| function ReportButtonImageResources(resIds) | |
| local function AddResourcesForPlatform(platform, dlc) | |
| dlc = dlc or platform | |
| for _, btn in pairs(TagToImageName) do | |
| local name = GetPlatformSpecificImageName(btn, platform) | |
| local resId = ResourceManager.GetResourceID(name) | |
| if ResourceManager.GetResourceIDAsString(resId) ~= "" then | |
| resIds[resId] = dlc | |
| end | |
| end | |
| end | |
| AddResourcesForPlatform("win32") | |
| AddResourcesForPlatform("ps4") | |
| AddResourcesForPlatform("ps5") | |
| AddResourcesForPlatform("xbox_one", "xbox") | |
| AddResourcesForPlatform("switch") | |
| end | |
| --- Returns the platform-specific image path and scale for the given button. | |
| --- | |
| --- @param btn string the button name | |
| --- @return string the platform-specific image path | |
| --- @return number the scale factor for the image | |
| function GetPlatformSpecificImagePath(btn) | |
| btn = const.ShortenedButtonNames[btn] or btn | |
| local path = "UI/DesktopGamepad/" | |
| local ext = ".tga" | |
| local btnimg = btn | |
| if ShouldShowPS4Images() then | |
| path = "UI/PS4/" | |
| btnimg = (const.XboxToPlayStationButtons[btn] or btn) | |
| elseif ShouldShowPS5Images() then | |
| path = "UI/PS5/" | |
| btnimg = (const.XboxToPlayStationButtons[btn] or btn) | |
| elseif Platform.xbox_one then | |
| path = "UI/XboxOne/" | |
| elseif Platform.xbox_series then | |
| path = "UI/XboxSeries/" | |
| elseif Platform.switch or g_TestUIPlatform == "switch" or GetPCActiveControllerType() == "switch" then | |
| path = "UI/Switch/" | |
| else -- win32 | |
| end | |
| return path .. btnimg .. ext, 500 | |
| end | |
| --- | |
| --- Returns the platform-specific image tag for the given button. | |
| --- | |
| --- @param btn string the button name | |
| --- @param scale number (optional) the scale factor for the image | |
| --- @return string the platform-specific image tag | |
| --- | |
| function GetPlatformSpecificImageTag(btn, scale) | |
| btn = const.ShortenedButtonNames[btn] or btn | |
| local path = "UI/DesktopGamepad/" | |
| local btnimg = btn | |
| if ShouldShowPS4Images() then | |
| path = "UI/PS4/" | |
| btnimg = (const.XboxToPlayStationButtons[btn] or btn) | |
| elseif ShouldShowPS5Images() then | |
| path = "UI/PS5/" | |
| btnimg = (const.XboxToPlayStationButtons[btn] or btn) | |
| elseif Platform.xbox_one then | |
| path = "UI/XboxOne/" | |
| elseif Platform.xbox_series then | |
| path = "UI/XboxSeries/" | |
| elseif Platform.switch or g_TestUIPlatform == "switch" or GetPCActiveControllerType() == "switch" then | |
| path = "UI/Switch/" | |
| else -- win32 | |
| end | |
| if scale then | |
| return string.format("<image %s%s.tga %d>", path, btnimg, tonumber(scale) or 1000) | |
| else | |
| return string.format("<image %s%s.tga>", path, btnimg) | |
| end | |
| end | |
| OnMsg.XInputInitialized = RecreateButtonsTagLookupTable | |
| RecreateButtonsTagLookupTable() | |
| -- lookup tags with Untranslated() | |
| const.TagLookupTable["tm"] = Untranslated("™") -- ( tm TM trademark trade mark ) symbol | |
| const.TagLookupTable["copyright"] = Untranslated("©") -- ( copyright (c) (C) copy right ) symbol | |
| const.TagLookupTable["registered"] = Untranslated("®") -- ( registered (r) (R) ) symbol | |
| const.TagLookupTable["nbsp"] = Untranslated(" ") -- ( non-breaking space nonbreaking space non breaking space ) symbol | |
| local replace_map = { | |
| ["`"] = "'", | |
| ["‘"] = "'", | |
| ["’"] = "'", | |
| ["“"] = "\"", | |
| ["”"] = "\"", | |
| ["–"] = "-", | |
| ["—"] = "-", | |
| ["−"] = "-", | |
| ["…"] = "...", | |
| } | |
| --- Replaces non-standard characters in the input string with their standard counterparts. | |
| --- | |
| --- @param s string The input string to replace non-standard characters in. | |
| --- @return string The input string with non-standard characters replaced. | |
| function ReplaceNonStandardCharacters(s) | |
| for k, v in pairs(replace_map) do | |
| s = s:gsub(k, v) | |
| end | |
| return s | |
| end | |
| changed = {} | |
| --- | |
| --- Fixes up preset text properties by replacing non-standard characters. | |
| --- | |
| --- This function iterates through all presets in the `Presets` table, and for each text property | |
| --- that has a non-standard character, it replaces the character with its standard counterpart. | |
| --- It then saves all modified presets to disk. | |
| --- | |
| --- @param none | |
| --- @return none | |
| --- | |
| function FixupPresetTs() | |
| local count = 0 | |
| local validation_start = GetPreciseTicks() | |
| PauseInfiniteLoopDetection("FixupPresetTs") | |
| local eval = prop_eval | |
| local dirty = {} | |
| for class_name, presets in pairs(Presets) do | |
| for _, group in ipairs(presets) do | |
| for _, preset in ipairs(group) do | |
| preset:ForEachSubObject(function(obj) | |
| for _, prop in ipairs(obj:GetProperties()) do | |
| if prop.editor == "text" and eval(prop.translate, obj, prop) then | |
| local t = obj:GetProperty(prop.id) | |
| if t and t ~= "" then | |
| local id, text = TGetID(t) or RandomLocId(), TDevModeGetEnglishText(t) | |
| local new_text = ReplaceNonStandardCharacters(text) | |
| if text ~= new_text then | |
| obj:SetProperty(prop.id, T(id, new_text)) | |
| table.insert(changed, preset.class .. " " .. new_text) | |
| dirty[class_name] = true | |
| count = count + 1 | |
| end | |
| end | |
| end | |
| end | |
| end) | |
| end | |
| end | |
| end | |
| for class_name in pairs(dirty) do | |
| _G[class_name]:SaveAll("force save all") | |
| end | |
| ResumeInfiniteLoopDetection("FixupPresetTs") | |
| CreateMessageBox(nil, Untranslated("Fixup Ts"), Untranslated( | |
| string.format("Changed a total of %d texts for %d ms", count, GetPreciseTicks() - validation_start))) | |
| end | |
| local diacritics_map = { | |
| ["À"] = "A", | |
| ["Á"] = "A", | |
| ["Â"] = "A", | |
| ["Ã"] = "A", | |
| ["Ä"] = "A", | |
| ["Å"] = "A", | |
| ["Æ"] = "AE", | |
| ["Ç"] = "C", | |
| ["È"] = "E", | |
| ["É"] = "E", | |
| ["Ê"] = "E", | |
| ["Ë"] = "E", | |
| ["Ì"] = "I", | |
| ["Í"] = "I", | |
| ["Î"] = "I", | |
| ["Ï"] = "I", | |
| ["Ð"] = "D", | |
| ["Ñ"] = "N", | |
| ["Ò"] = "O", | |
| ["Ó"] = "O", | |
| ["Ô"] = "O", | |
| ["Õ"] = "O", | |
| ["Ö"] = "O", | |
| ["Ø"] = "O", | |
| ["Ù"] = "U", | |
| ["Ú"] = "U", | |
| ["Û"] = "U", | |
| ["Ü"] = "U", | |
| ["Ý"] = "Y", | |
| ["Þ"] = "P", | |
| ["ß"] = "s", | |
| ["à"] = "a", | |
| ["á"] = "a", | |
| ["â"] = "a", | |
| ["ã"] = "a", | |
| ["ä"] = "a", | |
| ["å"] = "a", | |
| ["æ"] = "ae", | |
| ["ç"] = "c", | |
| ["è"] = "e", | |
| ["é"] = "e", | |
| ["ê"] = "e", | |
| ["ë"] = "e", | |
| ["ì"] = "i", | |
| ["í"] = "i", | |
| ["î"] = "i", | |
| ["ï"] = "i", | |
| ["ð"] = "eth", | |
| ["ñ"] = "n", | |
| ["ò"] = "o", | |
| ["ó"] = "o", | |
| ["ô"] = "o", | |
| ["õ"] = "o", | |
| ["ö"] = "o", | |
| ["ø"] = "o", | |
| ["ù"] = "u", | |
| ["ú"] = "u", | |
| ["û"] = "u", | |
| ["ü"] = "u", | |
| ["ý"] = "y", | |
| ["þ"] = "p", | |
| ["ÿ"] = "y", | |
| } | |
| --- | |
| --- Removes diacritical marks (accents, umlauts, etc.) from the given string. | |
| --- | |
| --- @param s string The input string to remove diacritical marks from. | |
| --- @return string The input string with diacritical marks removed. | |
| function RemoveDiacritics(s) | |
| return s:gsub("[%z\1-\127\194-\244][\128-\191]*", diacritics_map) | |
| end | |
| -------------------- Tokenization ------------------------- | |
| tag_processors = {} | |
| DefineClass.XTextToken = { | |
| __parents = { "PropertyObject" }, | |
| text = false, | |
| type = false, | |
| args = false, | |
| } | |
| -- Text -> Tokens | |
| --- | |
| --- Tokenizes the given input text into a stream of tokens. | |
| --- | |
| --- @param input_text string The input text to tokenize. | |
| --- @param token_func function (optional) A custom token processing function. If not provided, a default function will be used. | |
| --- @param stream table (optional) The output stream to append tokens to. If not provided, a new stream will be created. | |
| --- @return table The stream of tokens. | |
| function XTextTokenize(input_text, token_func, stream) | |
| if not token_func then | |
| stream = stream or {} | |
| token_func = function(stream, ttype, args, text) | |
| if text == "" then | |
| return | |
| end | |
| local next_token = XTextToken:new({type=ttype, text=text, args=args}) | |
| table.insert(stream, next_token) | |
| end | |
| end | |
| if type(input_text) ~= "string" or not utf8.IsValidString(input_text) then | |
| token_func(stream, "text", false, | |
| "Not a valid UTF-8 string:" .. string.gsub(input_text, "[^a-zA-Z0-9\n <%>-%:%(%)\\/]", ".")) | |
| return stream | |
| end | |
| local byte_idx = 1 | |
| local input_text_bytes_len = #input_text | |
| local tags_on = true | |
| while byte_idx <= input_text_bytes_len do | |
| local start_byte_idx, end_byte_idx = string.find(input_text, "</?[^%s=>][^>]*>", byte_idx) | |
| if not start_byte_idx then | |
| start_byte_idx = input_text_bytes_len + 1 | |
| end | |
| token_func(stream, "text", false, string.sub(input_text, byte_idx, start_byte_idx - 1)) | |
| byte_idx = start_byte_idx + 1 | |
| -- grab any available non text tokens | |
| if end_byte_idx then | |
| -- we're done | |
| byte_idx = end_byte_idx + 1 | |
| local token = string.sub(input_text, start_byte_idx + 1, end_byte_idx - 1) | |
| local elements | |
| if token:find("%s") then | |
| elements = {} | |
| for part in string.gmatch(token, "[^%s]+") do | |
| table.insert(elements, part) | |
| end | |
| -- collapse quoted strings | |
| local i = 1 | |
| while i <= #elements do | |
| if elements[i]:starts_with("'") then | |
| if i < #elements and not elements[i]:ends_with("'") then | |
| elements[i] = string.format("%s %s", elements[i], elements[i + 1]) | |
| table.remove(elements, i + 1) | |
| else | |
| if elements[i]:ends_with("'") then | |
| elements[i] = elements[i]:sub(2, -2) | |
| end | |
| i = i + 1 | |
| end | |
| else | |
| i = i + 1 | |
| end | |
| end | |
| end | |
| local tag = elements and elements[1] or token | |
| if tag == "tags" then | |
| tags_on = elements and elements[2] == "on" | |
| elseif tag == "literal" and tonumber(elements and elements[2]) then | |
| local offset = tonumber(elements[2]) | |
| offset = Min(Max(0, offset), input_text_bytes_len) | |
| byte_idx = Max(end_byte_idx + 1, Min(input_text_bytes_len + 1, end_byte_idx + offset + 1)) | |
| token_func(stream, "text", false, string.sub(input_text, end_byte_idx + 1, byte_idx - 1)) | |
| elseif token == "" or not tag_processors[tag] or tag == "text" or not tags_on then | |
| token_func(stream, "text", false, "<" .. token .. ">") | |
| else | |
| if elements then | |
| table.remove(elements, 1) | |
| end | |
| token_func(stream, tag, elements, token) | |
| end | |
| end | |
| end | |
| return stream | |
| end | |
| local starts_with = string.starts_with | |
| local string_gmatch = string.gmatch | |
| --- | |
| --- Counts the number of words in the given line of text. | |
| --- | |
| --- @param line string The input line of text to count words in. | |
| --- @return integer The number of words in the input line. | |
| function CountWords(line) | |
| local count = 0 | |
| for _, span in ipairs(XTextTokenize(line)) do | |
| if span.type == "text" then | |
| local text = span.text | |
| if not starts_with(text, "<") then | |
| for word in string_gmatch(text, "[^%s]+") do | |
| count = count + 1 | |
| end | |
| end | |
| end | |
| end | |
| return count | |
| end | |