如何用Rust nom优雅解析带标签文本?求优化现有代码
用Rust的nom库优化带标签文本解析
问题描述
需要用nom库解析带标签的文本,例如输入字符串aa<haha>test 1 2 3</haha> string 2,期望得到解析结果:文本aa、标签haha及其内容test 1 2 3、文本 string 2。现有实现代码但希望获得更优方案或简化方法。
原实现代码
use nom::branch::alt; use nom::bytes::complete::{*}; use nom::IResult; use nom::sequence::{delimited, separated_pair}; #[derive(Debug)] pub enum ElementType { Text(String), Tag(String, String), } fn parse_element_type_text(input: &str) -> IResult<&str, ElementType> { let (remaining, text) = take_till(|c: char| c == '<')(input)?; if text.is_empty() { Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Eof))) } else { Ok((remaining, ElementType::Text(text.to_string()))) } } fn parse_element_type_tag(input: &str) -> IResult<&str, ElementType> { let (left, tag_name) = delimited( tag("<"), take_until(">"), tag(">"), )(input)?; let (left, content) = take_until("</")(left)?; let (left, tag_name2) = delimited( tag("</"), tag(tag_name), tag(">"), )(left)?; Ok((left, ElementType::Tag(tag_name.to_string(), content.to_string()))) } fn parse_element(input: &str) -> IResult<&str, Vec<ElementType>> { let mut elements = vec![]; let mut input = input; loop { let original_input = input; match alt((parse_element_type_tag, parse_element_type_text))(input) { Ok((remaining_input, element)) => { elements.push(element); input = remaining_input; } Err(_) => break, } if original_input == input { break; } } Ok((input, elements)) } fn main() { let text = r#"<foo>some more text</foo> even more text!<tag2>test haha</tag2>"#; let result = parse_element(text); println!("{:?}", result); }
优化后的实现方案
主要优化点
- 用nom内置的
many0替代手动循环,简化代码结构 - 标签解析逻辑更贴合nom的组合式风格
- 文本解析直接用
take_till1(非空版本),省去手动判断空字符串的逻辑 - 统一错误处理,利用nom原生错误类型处理边界情况
优化代码
use nom::branch::alt; use nom::bytes::complete::{tag, take_till1, take_until}; use nom::IResult; use nom::multi::many0; use nom::sequence::delimited; #[derive(Debug)] pub enum ElementType { Text(String), Tag(String, String), } // 解析非空文本段,直到遇到< fn parse_text(input: &str) -> IResult<&str, ElementType> { take_till1(|c| c == '<')(input) .map(|(rem, text)| (rem, ElementType::Text(text.to_string()))) } // 解析完整标签:<tag>content</tag> fn parse_tag(input: &str) -> IResult<&str, ElementType> { // 解析开始标签,提取标签名 let (rem, tag_name) = delimited(tag("<"), take_until(">"), tag(">"))(input)?; // 提取标签内容,直到闭合标签开头 let (rem, content) = take_until("</")(rem)?; // 验证并跳过闭合标签 let (rem, _) = delimited(tag("</"), tag(tag_name), tag(">"))(rem)?; Ok((rem, ElementType::Tag(tag_name.to_string(), content.to_string()))) } // 批量解析所有元素 fn parse_elements(input: &str) -> IResult<&str, Vec<ElementType>> { many0(alt((parse_tag, parse_text)))(input) } fn main() { let text = r#"aa<haha>test 1 2 3</haha> string 2"#; match parse_elements(text) { Ok((remaining, elements)) => { println!("剩余未解析内容: '{}'", remaining); println!("解析结果: {:?}", elements); } Err(e) => println!("解析错误: {}", e), } }
优化说明
many0的使用:自动重复执行解析器直到失败,将结果收集到Vec中,彻底替代手动循环的冗余代码take_till1:强制匹配至少一个字符,直接避免原代码中空文本的判断逻辑,符合nom的错误处理机制- 职责单一的解析函数:每个函数只处理一种元素类型,代码结构更清晰,可读性和可维护性更强
- 规范错误处理:依赖nom原生错误类型,无需手动构造错误,错误信息更准确直观
内容的提问来源于stack exchange,提问作者Fajela Tajkiya
相关产品推荐
相关产品推荐

