Android 自定义解析html标签

用来解析类如下面代码里 html 标签样式.

html 复制代码
<span style="color: rgb(64, 169, 255);">文字内容</span>
<span style="color: rgb(64, 169, 255); font-size: 16px;"><strong>文字</strong></span>

使用:

Kotlin 复制代码
             val str = htmlStr.replace("span", CustomTagHandler.HANDLE_TAG)
             tvHtml.text =
                        HtmlCompat.fromHtml(
                        str,
                        HtmlCompat.FROM_HTML_MODE_COMPACT,
                        null,
                        CustomTagHandler()
                    )

CustomTagHandler.kt 文件:

Kotlin 复制代码
import android.graphics.Color
import android.text.Editable
import android.text.Html.TagHandler
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import org.xml.sax.XMLReader

/**
 * 自定义解析 html tag.
 *
 * 解析字符串样式:
 * 
 *     val str =
 *         """<p>
 *                <span style="color: rgb(64, 169, 255);">文字内容</span>
 *            </p>
 *            <p>
 *                <span style="color: rgb(64, 169, 255); font-size: 16px;"><strong>文字</strong></span>
 *            </p>
 *         """
 *
 * @Author: qq.yang
 * @Date: 2024/2/26 14:33
 */
internal class CustomTagHandler : TagHandler {

    companion object {
        const val HANDLE_TAG = "qqTag"
    }

    private var startIndex = 0
    private var stopIndex = 0
    private val attributes = HashMap<String, String>()

    override fun handleTag(
        opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader?
    ) {
        Logs.i("tagHandler -----> opening: $opening, tag: $tag, output: $output, xmlReader: ${xmlReader?.javaClass}")

        processAttributes(xmlReader)
        if (tag.equals(HANDLE_TAG, ignoreCase = true)) {
            if (opening) {
                startSpan(tag, output, xmlReader)
            } else {
                endSpan(tag, output, xmlReader)
                attributes.clear()
            }
        }
    }

    private fun startSpan(tag: String?, output: Editable, xmlReader: XMLReader?) {
        startIndex = output.length
    }

    private fun endSpan(tag: String?, output: Editable, xmlReader: XMLReader?) {
        stopIndex = output.length
        val style = attributes["style"]
        if (!style.isNullOrBlank()) {
            analysisStyle(startIndex, stopIndex, output, style)
        }
    }

    private fun processAttributes(xmlReader: XMLReader?) {
        xmlReader ?: return
        try {
            val elementField = xmlReader.javaClass.getDeclaredField("theNewElement")
            elementField.isAccessible = true
            val element = elementField[xmlReader]
            val attsField = element.javaClass.getDeclaredField("theAtts")
            attsField.isAccessible = true
            val atts = attsField[element]
            val dataField = atts.javaClass.getDeclaredField("data")
            dataField.isAccessible = true
            val data = dataField[atts] as Array<String>
            val lengthField = atts.javaClass.getDeclaredField("length")
            lengthField.isAccessible = true
            val len = lengthField[atts] as Int
            for (i in 0 until len) attributes[data[i * 5 + 1]] = data[i * 5 + 4]
        } catch (e: Exception) {
            // ignore.
        }
    }

    /**
     * 解析style属性
     */
    private fun analysisStyle(startIndex: Int, stopIndex: Int, editable: Editable, style: String?) {
        Logs.i("tagHandler -----> analysisStyle -----> style:$style")
        style ?: return
        val attrArray = style.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val attrMap: MutableMap<String, String> = HashMap()
        for (attr in attrArray) {
            val keyValueArray = attr.split(":".toRegex()).dropLastWhile { it.isEmpty() }
                .toTypedArray()
            if (keyValueArray.size == 2) {
                // 记住要去除前后空格
                attrMap[keyValueArray[0].trim { it <= ' ' }] =
                    keyValueArray[1].trim { it <= ' ' }
            }
        }
        Logs.i("tagHandler -----> analysisStyle -----> attrMap:$attrMap")

        val fontSizeAttr = attrMap["font-size"]
        val fontSize = fontSizeAttr?.removeSuffix("px")?.toFloatOrNull()?.dp
        fontSize?.let {
            editable.setSpan(
                AbsoluteSizeSpan(fontSize.toInt()),
                startIndex,
                stopIndex,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }

        val colorAttr = attrMap["color"]
        val fontColor = colorAttr?.let { parseColor(it) }
        fontColor?.let {
            editable.setSpan(
                ForegroundColorSpan(fontColor),
                startIndex,
                stopIndex,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
    }

    // rgb(64, 169, 255) -> Color
    private fun parseColor(rgbString: String): Int {
        Logs.i("tagHandler -----> parseColor -----> rgbString:$rgbString")

        // 移除字符串中的 "rgb(" 和 ")"
        val cleanedString = rgbString.replace("rgb(", "").replace(")", "")

        // 拆分字符串以获取红、绿、蓝分量值
        val components = cleanedString.split(", ").map { it.toInt() }
        val (red, green, blue) = components

        // 构建颜色值并返回
        return Color.rgb(red, green, blue)
    }
}
相关推荐
resh_people9 分钟前
开源鸿蒙平台 KMP_CMP 三方库「kotlinx.html」适配全流程
开源·html·harmonyos
Cici_ovo11 分钟前
手机数据删除原理、运行内存、闪存、机械硬盘完整原理详解
android·智能手机
hasty17 分钟前
HTML 已经转义,为何仍有 XSS?Sharp srcdoc 漏洞中的第二次解析
前端·html·xss
hi_LeTian30 分钟前
【Linphone】Ubuntu20.04 编译 Linphone5.2 Android SDK 适配RK3326硬解码完整教程(避坑版)
android
SoStraw35 分钟前
跨平台P2P通信SDK,Windows/Android/iOS/Linux/macOS一套API全兼容
android·linux·windows·跨平台·p2p·webrpc
IMPYLH1 小时前
HTML 的 <tfoot> 元素
前端·javascript·html
李游Leo1 小时前
《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》07:复杂3D场景的帧率、内存与资源性能优化【鸿蒙心迹】
android·3d·性能优化·harmonyos
天神哥哥啊1 小时前
unity联调注意事项-安卓
android·unity·游戏引擎
机核研创社1 小时前
polo 长袖短裤套装自动化产线:八台机器排队接力
android·java·自动化
JosieBook1 小时前
【数据库】MySQL 实战精通系列 · 第2篇:数据建模与建表实战
android·数据库·mysql