当前位置: 首页 > news >正文

使用并解析 OPML 格式的订阅列表来转移自己的 RSS 订阅(解析篇)

OPML 全称是 Outline Processor Markup Language ,即 大纲处理标记语言。目前流行于收集博客的 RSS 源,便于用户转移自己的订阅项目。

本文将介绍这个古老的格式,并提供一个 .NET 上的简易解析器。


本文是两个部分的第二篇,前者是理解 OPML 格式,此篇是解析此格式:

  • 概念篇
  • 解析篇(本文)

本文内容

    • OPML 格式
    • 创建适用于 RSS 的简易 OPML 模型
    • 从 OPML 文档中解析出模型
    • 使用此 OPML 模型

OPML 格式

在解析之前,最好先理解此格式的的元素组成和元素属性,所以如果你没有阅读 概念篇,请先前往阅读。

创建适用于 RSS 的简易 OPML 模型

我们先为模型创建基类 OpmlModel

为了方便在客户端应用中使用,可以使其继承自 INotifyPropertyChanged

namespace Walterlv.Rssman.Models
{
    public abstract class OpmlModel : NotificationObject
    {
        public void Deserialize(XElement element)
        {
            OnDeserializing(element);
        }

        protected abstract void OnDeserializing(XElement element);
    }
}
namespace Walterlv.Rssman.Models
{
    public abstract class NotificationObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (Equals(field, value)) return;
            field = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        [NotifyPropertyChangedInvocator]
        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

拿出我们关心的 outline 的属性来解析,于是有:

namespace Walterlv.Rssman.Models
{
    [DebuggerDisplay("RssOutline {Text,nq}, {XmlUrl,nq}, Count={Children.Count,nq}")]
    public sealed class RssOutline : OpmlModel
    {
        private string _text;
        private OutlineType _type;
        private string _xmlUrl;
        private string _htmlUrl;

        public string Text
        {
            get => _text;
            set => SetValue(ref _text, value);
        }

        public OutlineType Type
        {
            get => _type;
            set => SetValue(ref _type, value);
        }

        public string XmlUrl
        {
            get => _xmlUrl;
            set => SetValue(ref _xmlUrl, value);
        }

        public string HtmlUrl
        {
            get => _htmlUrl;
            set => SetValue(ref _htmlUrl, value);
        }

        public bool HasChildren => Children.Any();

        public ObservableCollection<RssOutline> Children { get; } = new ObservableCollection<RssOutline>();

        protected override void OnDeserializing(XElement element)
        {
            // 等待编写解析代码。
        }
    }
}

还有表示 OPML 文档的模型:

namespace Walterlv.Rssman.Models
{
    [DebuggerDisplay("RssOpml {Title,nq}, Count={Children.Count,nq}")]
    public sealed class RssOpml : OpmlModel
    {
        private string _title;

        public string Title
        {
            get => _title;
            set => SetValue(ref _title, value);
        }

        public ObservableCollection<RssOutline> Children { get; } = new ObservableCollection<RssOutline>();

        protected override void OnDeserializing(XElement element)
        {
            // 等待编写解析代码。
        }
    }
}

从 OPML 文档中解析出模型

在以上的模型代码中,我为基类留有 OnDeserializing 方法以供反序列化。

为了尽可能简化此博客的代码,参数我直接使用了 XElement 类型,以便在方法中使用 XPath 语法来解析。(当然,如果你是做库或者进行大型可维护项目的开发,这里就需要一些抽象了。)

现在,我们写一个新的静态类型 Opml 来解析 OPML 文档:

namespace Walterlv.Rssman.Services
{
    public static class Opml
    {
        public static async Task<RssOpml> ParseAsync(Stream stream)
        {
            var document = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None);
            var root = document.XPathSelectElement("opml");
            var opml = new RssOpml();
            opml.Deserialize(root);
            return opml;
        }
    }
}

于是,再补全模型 RssOpmlRssOutline 的反序列化部分:

// RssOpml.cs
protected override void OnDeserializing(XElement element)
{
    var title = element.XPathSelectElement("head/title");
    Title = title?.Value;

    var outlines = element.XPathSelectElements("body/outline");
    Children.Clear();
    foreach (var value in outlines)
    {
        var outline = new RssOutline();
        outline.Deserialize(value);
        Children.Add(outline);
    }
}
// RssOutline.cs
protected override void OnDeserializing(XElement element)
{
    var text = element.Attribute("text");
    Text = text?.Value;

    var type = element.Attribute("type");
    if (type != null && Enum.TryParse(type.Value, out OutlineType outlineType))
    {
        Type = outlineType;
    }

    var xmlUrl = element.Attribute("xmlUrl");
    XmlUrl = xmlUrl?.Value;

    var htmlUrl = element.Attribute("htmlUrl");
    HtmlUrl = htmlUrl?.Value;

    var outlines = element.XPathSelectElements("outline");
    Children.Clear();
    foreach (var value in outlines)
    {
        var outline = new RssOutline();
        outline.Deserialize(value);
        Children.Add(outline);
    }
}

注意,以上两个方法请分别填充到 RssOpml.csRssOutline.csOnDeserializing 方法中。

这里,所有的 XML 解析均使用的是 XPath 语法,关于 XPath 语法,可以阅读 XML 的 XPath 语法 - walterlv,关于如何使用 XPath 在 .NET 中读写 XML 文件,可以阅读 .NET 使用 XPath 来读写 XML 文件 - walterlv。

使用此 OPML 模型

当你把这些类都准备好,那么你就可以使用简单的几句话来完成 OPML 文档的解析了。

在 UWP 应用中,可以通过 StorageFile 来打开一个文件流:

var folder = Package.Current.InstalledLocation;
using (var stream = await folder.OpenStreamForReadAsync("sample-opml.xml"))
{
    var opml = await Opml.ParseAsync(stream);
    // 使用此 OPML 文档
}

在 .NET Framework 传统应用中,可以使用 File.Read 来打开一个文件流。

由于我们本文中创建的模型均实现了 INotifyPropertyChanged 接口,所以你甚至可以直接将 Opml.ParseAsync 的返回结果应用于绑定。


我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 使用并解析 OPML 格式的订阅列表来转移自己的 RSS 订阅(概念篇)
  • csproj 文件中那个空的 NuGetPackageImportStamp 是干什么的?
  • C#/.NET 中 Thread.Sleep(0), Task.Delay(0), Thread.Yield(), Task.Yield() 不同的执行效果和用法建议
  • WPF 中那些可跨线程访问的 DispatcherObject(WPF Free Threaded Dispatcher Object)
  • 在 Visual Studio Code 中为代码片段(Code Snippets)添加快捷键
  • 在 Visual Studio 中使用 EditorConfig 统一代码风格(含原生与插件)
  • 在 Visual Studio Code 中添加自定义的代码片段
  • 用 dotTrace 进行性能分析时,Timeline 打不开?无法启动进程?也许你需要先开启系统性能计数器的访问权限
  • 了解 .NET/C# 程序集的加载时机,以便优化程序启动性能
  • git 如何更可靠地解决冲突?
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • 文件被占用?系统自带的“资源监视器(resmon)”也能帮你找到占用它的真凶
  • Windows 系统文件资源管理器的命令行参数(如何降权打开程序,如何选择文件)
  • 为 .NET 各种开发工具设置网络代理,提升在大陆的网络性能
  • 如何在旧版本的 .NET Core / Framework 中使用 C# 8 的异步流(IAsyncDisposable / IAsyncEnumerable / IAsyncEnumerator)
  • 《剑指offer》分解让复杂问题更简单
  • HTTP中的ETag在移动客户端的应用
  • jquery cookie
  • js面向对象
  • Nacos系列:Nacos的Java SDK使用
  • nginx 负载服务器优化
  • php ci框架整合银盛支付
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • spark本地环境的搭建到运行第一个spark程序
  • Travix是如何部署应用程序到Kubernetes上的
  • webpack+react项目初体验——记录我的webpack环境配置
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 机器学习 vs. 深度学习
  • 力扣(LeetCode)965
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 前端自动化解决方案
  • 最简单的无缝轮播
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • # 安徽锐锋科技IDMS系统简介
  • #pragma 指令
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (力扣题库)跳跃游戏II(c++)
  • (顺序)容器的好伴侣 --- 容器适配器
  • (一)appium-desktop定位元素原理
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .NET gRPC 和RESTful简单对比
  • .net生成的类,跨工程调用显示注释
  • .NET中使用Protobuffer 实现序列化和反序列化
  • @ModelAttribute 注解
  • @TableId注解详细介绍 mybaits 实体类主键注解
  • [ NOI 2001 ] 食物链
  • [16/N]论得趣
  • [AIGC] MySQL存储引擎详解