#播客 #RSS #XML

badpod

用于处理播客不完整源数据的Rust库

23个版本

0.9.1 2024年6月30日
0.8.3 2024年4月25日
0.8.2 2024年2月11日
0.7.4 2023年12月11日
0.3.2 2022年11月28日

#593 in 解析实现

Download history 109/week @ 2024-04-22 10/week @ 2024-04-29 4/week @ 2024-05-20 244/week @ 2024-06-24 323/week @ 2024-07-01

1,352每月下载量

MIT/Apache

200KB
5.5K SLoC

badpod

用于处理播客不完整源数据的Rust库。

此库不应用于
❌ 处理规范、处理过的播客源数据
❌ 与数据库交互

此库可用于
✅ 解释外部源数据,通常来自未知来源
✅ 提供有关源数据内容的反馈

动机

在需要与数据库通信的后端服务器上,通常使用严格的模式。因此,如果加载不符合模式的外部源内容,可能无法成功反序列化该内容。

这种情况在播客领域非常常见,其中播客的RSS源可能

  • 结合多个标准(命名空间)
  • 缺少某些元素
  • 某些值的数据类型错误
  • 等。

在这种情况下,我们可能需要一个更灵活的中间模式:一个不会立即抛出错误以遇到意外值的模式,而是将能够反序列化的内容强加于它,并将失败反序列化的内容存储起来,以便进一步清理或分析。

用法

在项目中包含

cargo add badpod

这将包括在您的 Cargo.toml 文件中的库的最新版本。

反序列化

let rss = match badpod::from_str(feed_str) {
    Ok(rss) => rss,
    Err(_) => panic!("Something went terribly wrong."),
};

理论上,badpod::from_str 应仅在两种情况下返回错误

  • 源不是有效的XML
  • 源根元素不是 <rss>

特性

检查标签和属性的存在

badpod 中,每个表示 XML 标签的字段都是一个 Vec,每个表示 XML 属性的字段都是一个 Option。这是为了反映在 XML 中,标签 可以 重复,而属性——不能。我们不强制要求在源中包含什么或多少个标签——这是您自己决定的!使其更加灵活(而不是立即抛出错误)也允许为用户提供更好的反馈。

match (value_time_split.remote_item.len(), value_time_split.value_recipient.len()) {
    (0, 0) => println!("Either a single `<podcast:remoteItem>` element or one or more `<podcast:valueRecipient>` elements are required."),
    (1, 0) => println!("You are referencing a remote item! Awesome!"),
    (_, 0) => println!("Only a single `<podcast:remoteItem>` element is allowed."),
    (0, _) => println!("You are sharing value with others during this segment! Nice!"),
    (_, _) => println!("Either a single `<podcast:remoteItem>` element or one or more `<podcast:valueRecipient>` elements can be included."),
};

注意:所有表示标签的字段都使用单数形式命名,尽管它们是向量。

反序列化复杂的标签

badpod 将文本格式的复杂数据转换为更容易处理的形式。如果不可能,我们提供具有 Other 变体的枚举,这旨在表示无法反序列化的数据及其失败的原因。

match geo {
    podcast::Geo::Ok {
        // f64
        latitude,
        // f64
        longitude,
        // Option<f64>
        altitude,
        // Option<f64>
        uncertainty,
    } => {
        println!("Successfully extracted geographical coordinates!")
    }
    podcast::Geo::Other((s, reason)) => {
        println!("Could not parse coordinates from \"{s}\": {reason}.")
    }
};

Other 变体在具有许多变体的枚举中特别有用。如果您没有与 Other 匹配,您知道反序列化生成了合理预期的结果。

match language {
    Language::English(region) => println!("A variant of English!"),
    Language::Lithuanian => println!("Lithuanian!"),
    Language::Other((s, _)) => println!("Unexpected language code \"{s}\"."),
    _ => println!("Some other valid language!"),
};

但是,仅仅因为您匹配了一个不是 Other 的变体,并不意味着它对您来说是有效的值。例如,MimeEnclosureAudioOpus 作为其变体之一,但如果你要求源只包含由 Apple Podcasts 支持的格式的媒体文件,那么你将想要拒绝它。再次强调,badpod 仅是分析源的工具;您决定“正确”的源必须是什么样子。

标签感知反序列化

许多标签使用相同的数据类型,但以不同的方式编码。例如,<podcast:locked><itunes:explicit> 都本质上是布尔值,但前者序列化为 "yes"/"no",后者为 "true"/"false"。在 badpod 中,它们都被反序列化为 Bool

match channel.itunes_explicit.get(0) {
    Some(is_explicit) => {
        match is_explicit {
            Bool::Ok(b) => {
                println!("is explicit? \"{b}\"")
            }
            Bool::Other((s, reason)) => println!("could not parse \"{s}\": {reason}"),
        }
    }
    None => println!("<itunes:explicit> not found."),
};

打印枚举

尽管这个crate主要用于反序列化,但在某些情况下需要将枚举转换回字符串。在 badpod 中,所有枚举都实现了 Display 特性

// Outputs "cs".
println!("{}", Language::Czech);

// Outputs "en".
println!("{}", Language::English(LanguageEnglish::Default));

// Outputs "en-gb".
println!("{}", Language::English(LanguageEnglish::UnitedKingdom));

依赖项

~5.5–7.5MB
~155K SLoC