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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 03:17:16