You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用html5ever遍历DOM树替换元素的实现问题

解决方案

解决借用冲突问题

在调用traverse时,先克隆dom.document再传递即可避免可变借用与节点引用的冲突——Rc<Node>的clone仅增加引用计数,无额外性能开销:

self.traverse(&mut dom, dom.document.clone());

实现递归遍历与节点替换

traverse函数需要完成子节点遍历、<i18n>标签识别、目标<span>节点创建及替换逻辑,以下是完整实现:

use html5ever::{parse_fragment, QualName, ParseOpts, ns};
use markup5ever_rcdom::{RcDom, Node, NodeData, ElementData, Serializable, serialize};
use std::fs::File;
use std::io::Write;
use std::rc::Rc;
use std::cell::RefCell;

struct HtmlProcessor;

impl HtmlProcessor {
    fn expand_macro(&mut self) {
        let mut html = File::open("index.html").unwrap();
        let mut dom = parse_fragment(
            RcDom::default(),
            ParseOpts::default(),
            QualName::new(None, ns!(html), local_name!("div")),
            vec![],
        )
        .from_utf8()
        .read_from(&mut html)
        .unwrap();

        // 克隆document节点,规避借用冲突
        self.traverse(&mut dom, dom.document.clone());

        // 将修改后的DOM序列化回HTML并保存
        let mut output_file = File::create("modified_index.html").unwrap();
        serialize(&mut output_file, &dom.document, Default::default()).unwrap();
    }

    fn traverse(&mut self, dom: &mut RcDom, node: Rc<Node>) {
        // 先克隆子节点列表,避免遍历过程中修改原列表导致的迭代异常
        let children = {
            let node_data = node.borrow();
            match &node_data.data {
                NodeData::Element(elem) => elem.children.clone(),
                _ => vec![],
            }
        };

        for child in children {
            self.traverse(dom, child.clone());

            // 判断当前子节点是否为需要替换的<i18n>标签
            let child_data = child.borrow();
            if let NodeData::Element(elem) = &child_data.data {
                if elem.name.local == local_name!("i18n") && elem.name.ns == ns!(html) {
                    // 创建目标<span>元素
                    let span_name = QualName::new(None, ns!(html), local_name!("span"));
                    let mut span_elem = dom.create_element(span_name, vec![]);

                    // 设置class与data-location属性
                    let class_attr = QualName::new(None, ns!(), local_name!("class"));
                    dom.set_attribute(&mut span_elem, class_attr, "i18n".into());

                    let data_attr = QualName::new(None, ns!(), local_name!("data-location"));
                    dom.set_attribute(&mut span_elem, data_attr, "/index.html:$/i18n".into());

                    // 转移原<i18n>节点的子节点到<span>下
                    let mut child_mut = child.borrow_mut();
                    if let NodeData::Element(child_elem) = &mut child_mut.data {
                        let mut span_data = span_elem.borrow_mut();
                        if let NodeData::Element(span_elem_data) = &mut span_data.data {
                            span_elem_data.children.append(&mut child_elem.children);
                        }
                    }

                    // 找到父节点并完成子节点替换
                    let parent = child_data.parent.as_ref().unwrap().clone();
                    let mut parent_mut = parent.borrow_mut();
                    if let NodeData::Element(parent_elem) = &mut parent_mut.data {
                        if let Some(index) = parent_elem.children.iter().position(|n| Rc::ptr_eq(n, &child)) {
                            parent_elem.children[index] = span_elem;
                        }
                    }
                }
            }
        }
    }
}

替代实现思路

如果修改原有DOM树的逻辑过于繁琐,可采用构建新DOM树的方式:遍历原节点时,遇到<i18n>则生成对应<span>节点,其余节点原样复制。这种方式无需处理父节点替换逻辑,代码结构更简洁,适合复杂节点替换场景。

内容的提问来源于stack exchange,提问作者LittleSaya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 05:07:15