基于jsoup库自定义解析HTML源文件,提取元素中的内容以及属性(后续更新...)

概要

  这个需求其实是拿来优化的,因为之前设备不同,可能渲染复杂的WebView会导致字幕的轻微撕裂以及滚动效果的不加,所以后面决定使用原生Android来实现复杂的跑马灯效果,折腾了好久,请原谅我才刚入行的菜鸟。

整体架构流程

使用到的三方库

implementation 'org.jsoup:jsoup:1.14.3'  // 使用最新版本替换此处的1.14.3

在build.gradle(app)中导入这个用来解析HTML三方库

实现细节

1.通过jsoup库的解析,将指定元素下的节点以及子节点全部解析出来

2.涉及深度优先搜索策略,对DOM树的解析

3.涉及结构化实体类的搭建

第一步 1.jsoup库的解析以及使用(目前涉及到的只是针对assets文件下资源文件的解析,如果要解析本地路径,把相对应的替换即可,因为assets下的HTML文件好调试。)

  首先创建一个工具类 

AcquireTextMessageUtils   (给出核心代码部分)

 该工具类主要是运用jsoup库来实现对HTML的文件解析,一共包含两个方法,一个是可以获取资源文件assets中的HTML文件,一个获取本地指定路径下的文件。

  /**
     * 解析`assets`目录下HTML文件中指定父元素及其所有后代元素的属性,并返回结构化的元素数据列表。
     *    <即将使用的主要方法>
     * @param context           上下文对象
     * @param assetHtmlFileName `assets`目录下HTML文件名
     * @param parentSelector    父元素的CSS选择器
     * @return 结构化的元素数据列表,每个元素数据为一个`ElementData`对象
     */

    public static List<ElementData> parseChildElementsAndAttributes(Context context, String assetHtmlFileName, String parentSelector) {
        try {
            // 从assets目录读取HTML文件内容
            StringBuilder htmlStringBuilder = new StringBuilder();
            BufferedReader reader = new BufferedReader(new InputStreamReader(context.getAssets().open(assetHtmlFileName), StandardCharsets.UTF_8));
            String line;
            while ((line = reader.readLine()) != null) {
                htmlStringBuilder.append(line).append("\n");
            }
            reader.close();

            String htmlContent = htmlStringBuilder.toString();

            // 解析HTML内容为Document对象
            Document doc = Jsoup.parse(htmlContent);

            // 选取指定父元素
            Element parentElement = doc.getElementById(parentSelector);

            if (parentElement != null) {
                // 递归遍历父元素及其所有后代节点,获取结构化的元素数据列表
                return parseElementAndAttributes(parentElement, 0);
            } else {
                Log.w("AcquireTextMessageUtils", "Parent element not found for selector: " + parentSelector);
                return new ArrayList<>();
            }
        } catch (IOException e) {
            Log.e("AcquireTextMessageUtils", "Error reading or parsing HTML file:", e);
            return new ArrayList<>();
        }
    }

    private static List<ElementData> parseElementAndAttributes(Element element, int depth) {
        List<ElementData> elementDataList = new ArrayList<>();
        StringBuilder indent = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            indent.append("\t");
        }

        ElementData elementData = new ElementData();
        elementData.setDepth(depth);
        elementData.setIndent(indent.toString());
        elementData.setTagName(element.tagName());

        Attributes attributes = element.attributes();
        for (Attribute attribute : attributes) {
            elementData.getAttributes().put(attribute.getKey(), attribute.getValue());
        }
        for (Node child : element.childNodes()) {
            if (child instanceof Element) {
                elementData.getChildNodes().addAll(parseElementAndAttributes((Element) child, depth + 1));
            } else if (child instanceof TextNode) {
                // 处理 TextNode
                ElementData otherNodeData = new ElementData();
                otherNodeData.setDepth(depth + 1);
                otherNodeData.setIndent(indent.toString());
                otherNodeData.setTextContent(((TextNode) child).text());
                elementData.getChildNodes().add(otherNodeData);
            } else {
                // 处理其他非 Element 节点(如 CommentNode 等)
                ElementData otherNodeData = new ElementData();
                otherNodeData.setDepth(depth + 1);
                otherNodeData.setIndent(indent.toString());

                // 修改此处,使用合适的文本表示方法而非 outerHtml()
                otherNodeData.setTextContent(child.toString()); // 或者使用 child.nodeName()、child.baseUri() 等,取决于您希望存储的内容

                elementData.getChildNodes().add(otherNodeData);
            }
        }

        elementDataList.add(elementData);

        return elementDataList;
    }



    /**
     * 增加一个方法,从本地DOWNLOAD解析文件
     * 类似于上述方法,由于不需要从本app的文件读取资源文件,所以剔除了context
     * */
    public static List<ElementData> parseChildElementsAndAttributes(String filePath, String parentSelector) {
        try {
            // 从本地路径读取HTML文件内容
            StringBuilder htmlStringBuilder = new StringBuilder();
            FileInputStream fis = new FileInputStream(filePath);
            BufferedReader reader = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8));
            String line;
            while ((line = reader.readLine()) != null) {
                htmlStringBuilder.append(line).append("\n");
            }
            reader.close();
            fis.close();

            String htmlContent = htmlStringBuilder.toString();

            // 解析HTML内容为Document对象
            Document doc = Jsoup.parse(htmlContent);

            // 选取指定父元素
            Element parentElement = doc.getElementById(parentSelector);
            //对指定的父元素再缓存或者源文件中插入display:none属性
            if (parentElement != null) {
                // 递归遍历父元素及其所有后代节点,获取结构化的元素数据列表
                return parseElementAndAttributes(parentElement, 0);
            } else {
                Log.w("AcquireTextMessageUtils", "Parent element not found for selector: " + parentSelector);
                return new ArrayList<>();
            }
        } catch (IOException e) {
            Log.e("AcquireTextMessageUtils", "Error reading or parsing HTML file:", e);
            return new ArrayList<>();
        }
    }

方法实现起来比较简单,没有做过多CSS解释器的处理,只是单纯的解析目标元素以及子节点 。

当然解析只是第一步,只是负责将目标元素从HTML文件中提取出来,你还需要对数据进行处理方便你后续对该数据的解析以及正则表达。

第二步:对数据的处理

创建

HTMLMessageExtractor类

可以看到如下调用,

   public static ArrayList<String> getHtmlMessage(String filePath, String parentSelector, StyleTextUpdate styleTextUpdate, DisplayMetrics displayMetrics) {
        List<ElementData> elementDataList = AcquireTextMessageUtils.parseChildElementsAndAttributes(filePath, parentSelector);
        return collectElementDataRecursively(elementDataList, new ArrayList<>(), false, false, styleTextUpdate, displayMetrics);
    }


 private static ArrayList<String> collectElementDataRecursively(List<ElementData> elementDataList, List<String> accumulatedAttributes, boolean isParentInsideEm, boolean isParentStrong, StyleTextUpdate styleTextUpdate, DisplayMetrics displayMetrics) {
        ArrayList<String> logMessages = new ArrayList<>();

        for (ElementData elementData : elementDataList) {
            // 合并当前元素的属性到累积属性中
            List<String> combinedAttributes = new ArrayList<>(accumulatedAttributes);
            combinedAttributes.add(elementData.getAttributes().get("style"));

            // 获取文本内容,如果为空,则使用"无文本内容"代替
            String textContent = elementData.getTextContent() != null ? elementData.getTextContent() : "";
          //-----------------------这里根据需要,你可以对你需要的标签做进一步处理,比如对strong em标签做标记以便后续处理。
            // 构建属性字符串
            StringBuilder attrStrBuilder = new StringBuilder();

            for (String entry : combinedAttributes) {
                attrStrBuilder.append(entry).append("; ");
            }
            if (attrStrBuilder.length() > 0) {
                attrStrBuilder.setLength(attrStrBuilder.length() - 2); // 移除最后一个分号和空格
            }
            String logMessagess = "   深度" + elementData.getDepth() + "   TagName:[" + elementData.getTagName() + "]    文本内容: {" + textContent + "}; 属性: " + attrStrBuilder + (isInsideEm ? "<em>" : "") + "     " + (isStrong ? "<strong>" : "");//+" <--->  "+attrStrBuilder+(isStrong?"<strong>":"")
//            Log.d("AcquireTextMessageUtils", "AcquireTextMessageUtils data:" + logMessagess);
            int startIndex = styleTextUpdate.getLength();
//            styleTextUpdate.appendContent(textContent);
            // 收集逻辑,包括强调状态
            if (!textContent.isEmpty()) {
                String logMessage = "   深度" + elementData.getDepth() + "   TagName:[" + elementData.getTagName() + "]    文本内容: {" + textContent + "}; 属性: " + attrStrBuilder + (isInsideEm ? "<em>" : "") + "     " + (isStrong ? "<strong>" : "");
                Log.d("AcquireTextMessageUtils", "AcquireTextMessageUtils data:" + logMessage);
                styleTextUpdate.enqueueLogContent(textContent, combinedAttributes, isInsideEm, isStrong, displayMetrics);
                logMessages.add(logMessage);
            }

            // 递归调用,传递累积的属性和当前的强调状态,并收集返回的日志消息
            if (elementData.getChildNodes() != null && !elementData.getChildNodes().isEmpty()) {
                ArrayList<String> childMessages = collectElementDataRecursively(elementData.getChildNodes(), combinedAttributes, isInsideEm, isStrong, styleTextUpdate, displayMetrics);
                logMessages.addAll(childMessages);
            }
            int endIndex = styleTextUpdate.getLength();

        }

        return logMessages;
    }

 第三步对数据进行进一步处理,对样式进行修改

styleTextUpdate类
 private SpanTextView textView2;
    private SpannableStringBuilder accumulatedText = new SpannableStringBuilder();
    private static final String TAG = "StyleTextUpdate";
    private   int as=0;
    public StyleTextUpdate(SpanTextView textView) {
        this.textView2 = textView;
    }


    public void enqueueLogContent(String content, List<String> styles, boolean isInsideEm, boolean isInsideStrong, DisplayMetrics displayMetrics) {

          as++;
          Log.d("StyleTextUpdate","循环次数"+as);
//        Log.d("StyleTextUpdate","---》内容:"+content+"  ---->样式"+styleInfo+" -----s"+content.length());
        // 添加内容到累积文本
        accumulatedText.append(content);
        stylesFlag = new int[12];
        // 应用样式到新添加的文本上
        for (int i = styles.size() - 1; i >= 0; i--) {
            String styleInfo = styles.get(i);
            if (styleInfo == null) {
                continue;
            }
            applyStyles(styleInfo, accumulatedText.length() - content.length(), accumulatedText.length(), displayMetrics);
            int a = accumulatedText.length() - content.length();
            Log.d("StyleTextUpdate", "   index:" + a + "   ---" + accumulatedText.length());
        }
      //这里做你需要的标签处理

        // 设置到TextView
        textView2.setText(accumulatedText);

    }

    public int getLength() {
        return accumulatedText.length();
    }

可以看到这里用

SpannableStringBuilder做字符串拼接

小结

说白了到这里,基础的框架已经搭建完了,具体实现的地方可以自己自定义,上述给出的是部分代码,如果需要源码,请移步到我github地址:GitHub - CzZmessages/ailiyunOSS: this is ailiyunDownload 这是一个阿里云的下载代码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值