使用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
相关产品推荐
相关产品推荐

