使用Rust nom解析带有标签的文本的正确方法是什么?

xpcnnkqh  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(100)

我想用标签解析文本。例如,对于字符串aa<haha>test 1 2 3</haha> string 2,结果应该是字符串aa、内容为test 1 2 3的标记haha和字符串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);
}
qgzx9mmu

qgzx9mmu1#

作为一个侧记,你应该总是包括链接到操场与这样的问题。如果你不知道,rust playground可以在here中找到,它允许你在浏览器中运行代码并与他人共享。您可以通过右上角的共享按钮共享特定的代码片段,然后保存永久链接。
你的方法的主要问题是它不处理递归标记结构。标签可能包含其他子标签,对吗?如果是这样的话,你会遇到take_until("</")的问题,这是由cafce25提到的-它会跳过任何其他标记:

fn main() {
    let text = r#"<foo><bar>hello</bar></foo>"#;
    let result = parse_element(text);
    println!("{:?}", result);
}

Playground
上面的代码片段返回Ok(("<foo><bar>hello</bar></foo>", [])),因为结束标记"<\bar>"与开始标记"<foo>"不匹配。
然而,这个问题不仅仅是take_until的使用。为了支持嵌套标签,你的枚举必须递归定义:

#[derive(Debug)]
pub enum ElementType {
    Text(String),
    Tag(String, Vec<ElementType>),
}

(note编译器允许这样做,因为当ElementType递归地包含自己时,它在Vec<ElementType>后面,Vec<ElementType>的行为方式与Box<ElementType>相同-实际的内部ElementType存储在堆中,原始类型仅包含指向子ElementType s的指针)
这是因为标签可以包含任意数量的子标签。因此,解析必须递归地进行:

fn parse_element_type_tag(input: &str) -> IResult<&str, ElementType> {
    let (left, tag_name) = delimited(
        tag("<"),
        take_until(">"),
        tag(">"),
    )(input)?;
    let (left, content) = parse_element(left)?;
    let (left, tag_name2) = delimited(
        tag("</"),
        tag(tag_name),
        tag(">"),
    )(left)?;

    Ok((left, ElementType::Tag(tag_name.to_string(), content)))
}

Playground
使用Vec的缺点是遍历创建的树变得更加复杂,这可以在使用递归标记的示例的打印值中看到:
Ok(("", [Tag("foo", [Tag("bar", [Text("hello")])])]))
这可以通过为不包含任何内容的标签和仅包含单个元素的标签添加额外的枚举变体来补救,但是这将高度依赖于您的用例,并且可能使事情变得更加复杂。

相关问题