Android 自定义解析html标签

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

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

使用: 

             val str = htmlStr.replace("span", CustomTagHandler.HANDLE_TAG)
             tvHtml.text =
                        HtmlCompat.fromHtml(
                        str,
                        HtmlCompat.FROM_HTML_MODE_COMPACT,
                        null,
                        CustomTagHandler()
                    )

CustomTagHandler.kt 文件:


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)
    }
}

上一篇:docker:chown socket at step GROUP: No such process


下一篇:TechTool Pro for Mac v19.0.3中文激活版 硬件监测和系统维护工具