local strlen = string.len local strFormat = string.format local strFind = string.find local mathFloor = math.floor local tableInsert = table.insert local next = next local MediumFontSize = CS.XGame.ClientConfig:GetInt("NoticeMediumFontSize") local NodeType = { Head = 1, Tail = 2, } local ParagraphType = { Pic = 1, Text = 2, } local FontSizeMap = { ["xx-small"] = mathFloor(MediumFontSize * 0.6), ["x-small"] = mathFloor(MediumFontSize * 0.75), ["small"] = mathFloor(MediumFontSize * 0.89), ["medium"] = MediumFontSize, ["large"] = mathFloor(MediumFontSize * 1.2), ["x-large"] = mathFloor(MediumFontSize * 1.5), ["xx-large"] = mathFloor(MediumFontSize * 2), } local FontSizeNumMap = { [1] = mathFloor(MediumFontSize * 0.6), [2] = mathFloor(MediumFontSize * 0.75), [3] = mathFloor(MediumFontSize * 0.89), [4] = MediumFontSize, [5] = mathFloor(MediumFontSize * 1.2), [6] = mathFloor(MediumFontSize * 1.5), [7] = mathFloor(MediumFontSize * 2), } local HtmlCharMap = { ["""] = "\"", ["&"] = "&", ["<"] = "<", [">"] = ">", [" "] = " ", ["
"] = "\n", } local AlignMap = { ["center"] = CS.UnityEngine.TextAnchor.MiddleCenter, ["left"] = CS.UnityEngine.TextAnchor.MiddleLeft, ["right"] = CS.UnityEngine.TextAnchor.MiddleRight, ["justify"] = CS.UnityEngine.TextAnchor.MiddleLeft, } local function FilterSpecialSymbol(content) if not content then return end return content:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", function(symbol) return "%" .. symbol end) end local function RemoveBlank(content) if not content then return end return content:gsub("^[ \t\n\r]+", ""):gsub("[ \t\n\r]+$", "") end local function RemoveNewLine(content) return content:gsub("\n", " ") end local function RemoveInvalidChar(content) if not content then return "" end return content:gsub("", ""):gsub("", ""):gsub("", "") end local function GetBody(content) local i, _, _, subContent = strFind(content, "]-)>(.-)") if not i then return "" end return subContent end local function GetImg(content) local _, _, _, imgType, imgData = strFind(content, "%s]+)(%s-)(.-)>") if tag then local nodeType if tag:sub(1, 1) == "/" then nodeType = NodeType.Tail tag = tag:sub(2) else nodeType = NodeType.Head end tableInsert(tagList, { NodeType = nodeType, BeginPos = i + nextOffset, EndPos = j + nextOffset, Tag = tag, Param = param }) if j == #tempContent then return end nextOffset = nextOffset + j tempContent = tempContent:sub(j + 1) else return end count = count + 1 end end local function Match(tagList, matchedList) -- 构建tag匹配关系 local matchingList = {} for _, v in ipairs(tagList) do if v.NodeType == NodeType.Head then tableInsert(matchingList, v) elseif v.NodeType == NodeType.Tail then for j = #matchingList, 1, -1 do local head = matchingList[j] table.remove(matchingList, j) if v.Tag == head.Tag then tableInsert(matchedList, { Head = head, Tail = v }) break end end end end table.sort(matchedList, function(l, r) return l.Head.BeginPos < r.Head.BeginPos end) end local CreateChildren CreateChildren = function(parent, matchedList) if not parent then return end local count = 0 while #matchedList > 0 or count > 10000 do local curNode = matchedList[1] if parent.Tail.EndPos > curNode.Tail.EndPos then parent.Childs = parent.Childs or {} table.remove(matchedList, 1) tableInsert(parent.Childs, curNode) CreateChildren(curNode, matchedList) else return end count = count + 1 end end local function CreateTree(matchedList, tagTree) local count = 0 while #matchedList > 0 or count > 10000 do local curNode = matchedList[1] table.remove(matchedList, 1) CreateChildren(curNode, matchedList) tableInsert(tagTree, curNode) count = count + 1 end return tagTree end local function GetNodeStyle(node, content, defaultStyle) local style = {} local nodeParam = content:sub(node.Head.BeginPos, node.Head.EndPos) local tag = FilterSpecialSymbol(node.Head.Tag) local _, _, _, paramStr = strFind(nodeParam, "<" .. tag .. "(%s-)(.-)>") local findColor = false local findSize = false if not string.IsNilOrEmpty(paramStr) then local _, _, styleParamStr = strFind(paramStr, "style=\"(.-)\"") if not string.IsNilOrEmpty(styleParamStr) then local _, _, _, colorR, _, colorG, _, colorB = strFind(styleParamStr, "color:(%s-)rgb%((%d+),(%s-)(%d+),(%s-)(%d+)%)") if colorR and colorG and colorB then findColor = true style.ColorStr = ConvertRGB2Hex(colorR, colorG, colorB) end local _, fontSize _, _, _, fontSize = strFind(styleParamStr, "font%-size:(%s-)(.-);") if not fontSize then _, _, fontSize = strFind(styleParamStr, "font%-size:(%d*)") end if fontSize then findSize = true local pSize, pUnit _, _, pSize, pUnit = strFind(fontSize, "(%-?%d+%.*%d*)(%a*)") if pSize then --pt 转 px if string.lower(pUnit) == "px" then fontSize = pSize end --fontSize = tonumber(pSize) * 4 / 3 style.FontSize = fontSize else style.FontSize = RemoveBlank(fontSize) end end local isBlod = strFind(styleParamStr, "font%-weight:(%s-)bold") if isBlod then style.IsBlod = isBlod end end if node.Head.Tag == "font" then if not findColor then local _, _, colorStr = strFind(paramStr, "color=\"#(.-)\"") style.ColorStr = colorStr end if not findSize and node.Head.Tag == "font" then local _, _, fontSize = strFind(paramStr, "size=\"(.-)\"") style.FontSize = fontSize end end end style.ColorStr = style.ColorStr or defaultStyle.ColorStr style.FontSize = style.FontSize or defaultStyle.FontSize style.IsBlod = defaultStyle.IsBlod or style.IsBlod or node.Head.Tag == "b" if node.Head.Tag == "a" then style.IsHref = true local _, _, hrefParam = string.find(paramStr, "href=\"(.-)\"") if hrefParam then style.HrefParam = hrefParam end else style.IsHref = defaultStyle.IsHref end return style end local InitTextNode InitTextNode = function(node, content, defaultStyle, textNodes) local style = GetNodeStyle(node, content, defaultStyle) local lastEndPos = node.Head.EndPos if node.Childs then for _, child in ipairs(node.Childs) do if child.Head.BeginPos - lastEndPos > 1 then local text = content:sub(lastEndPos + 1, child.Head.BeginPos - 1) text = RemoveNewLine(text) if not string.IsNilOrEmpty(text) then tableInsert(textNodes, { Text = text, Style = style }) end end lastEndPos = child.Tail.EndPos InitTextNode(child, content, style, textNodes) end end if node.Tail.BeginPos - lastEndPos > 1 then local text = content:sub(lastEndPos + 1, node.Tail.BeginPos - 1) text = RemoveNewLine(text) if not string.IsNilOrEmpty(text) then tableInsert(textNodes, { Text = text, Style = style }) end end end local function CreateTextNode(paragraph, content) local textNodes = {} local tagTree = paragraph.Childs or {} if #tagTree <= 0 then if paragraph.Tail.BeginPos - paragraph.Head.EndPos > 1 then table.insert(textNodes, { Text = content:sub(paragraph.Head.EndPos + 1, paragraph.Tail.BeginPos - 1) }) end end for i = 1, #tagTree do if i == 1 then if tagTree[i].Head.BeginPos - paragraph.Head.EndPos > 1 then local tempText = content:sub(paragraph.Head.EndPos + 1, tagTree[i].Head.BeginPos - 1) if not string.IsNilOrEmpty(tempText) then table.insert(textNodes, { Text = tempText }) end end else if tagTree[i].Head.BeginPos - tagTree[i - 1].Head.EndPos > 1 then local tempText = content:sub(tagTree[i - 1].Tail.EndPos + 1, tagTree[i].Head.BeginPos - 1) if not string.IsNilOrEmpty(tempText) then table.insert(textNodes, { Text = tempText }) end end end InitTextNode(tagTree[i], content, {}, textNodes) end if #tagTree > 0 then if paragraph.Tail.BeginPos - tagTree[#tagTree].Tail.EndPos > 1 then local tempText = content:sub(tagTree[#tagTree].Tail.EndPos + 1, paragraph.Tail.BeginPos - 1) if not string.IsNilOrEmpty(tempText) then table.insert(textNodes, { Text = tempText }) end end end return textNodes end local function GenerateText(textNodes) local lastNode = textNodes[#textNodes] lastNode.Text = RemoveNewLine(lastNode.Text) if lastNode.Text:sub(-4) == "
" then if lastNode.Text == "
" then lastNode.Text = "" else lastNode.Text = lastNode.Text:sub(1, -5) end end local fontSize local str = "" local sourceStr = "" for _, v in ipairs(textNodes) do local tempStr = CS.XTool.ReplaceNoBreakingSpace(RemoveNewLine(v.Text)) sourceStr = sourceStr .. tempStr if v.Style then if v.Style.IsHref then local hrefParam = v.Style.HrefParam or "" tempStr = "" .. tempStr .. "" end if v.Style.FontSize then local fontSizePx = FontSizeMap[v.Style.FontSize] or FontSizeNumMap[tonumber(v.Style.FontSize)] or tonumber(v.Style.FontSize) if fontSizePx then tempStr = "" .. tempStr .. "" if not fontSize then fontSize = fontSizePx else if fontSizePx > fontSize then fontSize = fontSizePx end end end end if v.Style.IsBlod then tempStr = "" .. tempStr .. "" end if v.Style.ColorStr and not v.Style.IsHref then tempStr = "" .. tempStr .. "" end end str = str .. tempStr end for k, v in pairs(HtmlCharMap) do str = str:gsub(k, v) sourceStr = sourceStr:gsub(k, v) end return str, sourceStr, fontSize or FontSizeMap["xx-large"] end local function CreateParagraph(paragraph, content) local tempContent = content:sub(paragraph.Head.EndPos + 1, paragraph.Tail.BeginPos - 1) local _, imgData = GetImg(tempContent) if imgData then local texture = CS.UnityEngine.Texture2D(1, 1) texture:LoadImage(CS.System.Convert.FromBase64String(imgData)) return { Type = ParagraphType.Pic, Data = texture } end -- 文本段落 local textNodes = CreateTextNode(paragraph, content) if not textNodes or not next(textNodes) then return end local text, sourceText, fontSize = GenerateText(textNodes) return { Type = ParagraphType.Text, Data = text, SourceData = sourceText, FontSize = fontSize, Param = content:sub(paragraph.Head.BeginPos + 1, paragraph.Head.EndPos - 1) } end local function Deserialize(html) local content = RemoveInvalidChar(html) if string.IsNilOrEmpty(content) then return end local bodyContent = GetBody(content) if string.IsNilOrEmpty(bodyContent) then return end local tagList = {} InitAllTagList(bodyContent, tagList) local matchedList = {} Match(tagList, matchedList) local tagTree = {} CreateTree(matchedList, tagTree) local paragraphObjs = {} for _, v in ipairs(tagTree) do local paragraphObj = CreateParagraph(v, bodyContent) if paragraphObj then table.insert(paragraphObjs, paragraphObj) end end return paragraphObjs end local XHtmlHandler = { Deserialize = Deserialize, RemoveBlank = RemoveBlank, ParagraphType = ParagraphType, FontSizeMap = FontSizeMap, AlignMap = AlignMap, FilterSpecialSymbol = FilterSpecialSymbol, } return XHtmlHandler